#!/bin/sh

# Copyright - negotiable
#
# VERBATUM_COPYRIGHT_HEADER_INCLUDE_NEGOTIABLE
# Easy-TLS -- A Shell-based Easy-RSA extension utility to help manage
#               * OpenVPN specific TLS keys
#               * Easy-RSA based x509 security credentials
#               * Verified 'inline' combined OpenVPN node packages
#
# Copyright (C) 2020 Richard Bonhomme (Friday 13th of March 2020)
# https://github.com/TinCanTech/easy-tls
# tincantech@protonmail.com
# All Rights reserved.
#
# This code is released under version 2 of the GNU GPL
# See LICENSE of this project for full licensing details.
#
# Acknowledgement:
# This utility is "written in the style of" and "borrows heavily from" Easy-RSA
#
# Easy-TLS is inspired by syzzer
# See: https://github.com/OpenVPN/openvpn/blob/master/doc/tls-crypt-v2.txt
#
# VERBATUM_COPYRIGHT_HEADER_INCLUDE_NEGOTIABLE
#

# Easy-TLS requirements:
# + Easy-RSA Version 3.0.8
# + OpenVPN Version 2.5

# Set the Easy-TLS version
easytls_version () {
	unset -v EASYTLS_VERBOSE
	config_use || :
	print "Easy-TLS ${EASYTLS_VERSION} (${EASYTLS_STATUS})"
} # => easytls_version ()



############################################################################
#
# EASYTLS HELP Section
#

# Help/usage output to stdout
usage() {
	print "
Easy-TLS usage and overview

USAGE: easytls [options] COMMAND [command-options]

A list of commands is shown below. To get detailed usage and help for a
command, use:
  ./easytls help COMMAND

For a listing of options that can be supplied before the command, use:
  ./easytls help options

For a list of abbreviated command names, use:
  ./easytls help abb

For a list of configurable options, use:
  ./easytls help config

Here is the list of commands available with a short syntax reminder.
Use the 'help' command above to get full usage details.

  build     :Inter-active menu to build TLS keys
  inline    :Inter-active menu to build Inline files
  remove    :Inter-active menu to remove TLS keys and Inline files
  script    :Inter-active menu to configure Server scripts
  selfsign  :Inter-active menu to build and inline self-signed certificates

  init   | init-tls <hash_algorithm> no-ca
  cf     | config
         |
  sss    | self-sign-server <server_filename_base> (No-CA mode only)
  ssc    | self-sign-client <client_filename_base> (No-CA mode only)
         |
  bta    | build-tls-auth
  btc    | build-tls-crypt
  bc2s   | build-tls-crypt-v2-server <server_common_name>
  bc2c   | build-tls-crypt-v2-client
         |     <server_common_name> <client_common_name> <HW-ADDR> <IP-ADDR>
         |
  ita    | inline-tls-auth <filename_base> <key_direction> [ cmd-opts ]
  itc    | inline-tls-crypt <filename_base> [ cmd-opts ]
  ic2    | inline-tls-crypt-v2 <filename_base> [ cmd-opts ]
         |
  bc2gc  | build-tls-crypt-v2-group-client
         |     <server_common_name> <client_group_name> <HW-ADDR> <IP-ADDR>
  ic2gc  | inline-tls-crypt-v2-group-client
         |     <client_common_name> <client_group_name> [ cmd-opts ]
         |
  s      | status [ cmd-opts ]
  rk     | remove-tlskey <client_filename_base>
  rgk    | remove-group-tlskey <client_filename_base> <Group-Name>
  ri     | remove-inline <filename_base>
  rgi    | remove-group-inline <Client-Name> <Group-Name>
  is     | inline-show <filename_base>
         | inline-index-rebuild
  ix     | inline-expire <filename_base>
  cx     | cert-expire <filename_base> | <ca>
  d      | disable <filename_base> (Or display the current disabled list)
  e      | enable <filename_base> (Or display the current disabled list)
         | disabled-list-rehash
  sid    | save-id
  ver    | version
         | upgrade

Easy-TLS also has a useful Howto and wiki with expanded help and examples:
* https://github.com/TinCanTech/easy-tls/blob/master/easytls-howto-ii.md
* https://github.com/TinCanTech/easy-tls/wiki"

	# collect/show dir status:
	err_source="Not defined: vars autodetect failed and no value provided"
	work_dir="${EASYRSA:-$err_source}"
	pki_dir="${EASYRSA_PKI:-$err_source}"
	tls_dir="${EASYTLS_PKI:-$err_source}"
	print "
DIRECTORY STATUS (commands would take effect on these locations)
  EASYTLS: ${work_dir}
      PKI: ${pki_dir}
      TLS: ${tls_dir}"
} # => usage()

# Detailed command help
# When called with no args, calls usage(), otherwise shows help for a command
cmd_help() {
	text=""
	opts=""
	case "${1}" in
		init|init-tls) text="
  init-tls <hash_algorithm> no-ca
      Removes & re-initializes the TLS-key directory.
      * Easy-RSA is also required to be initialised.

      <hash_algorithm> - Specify a hash algorithm.
      Default SHA256 - Alternative SHA1

      nc|noca|no-ca - Use Easy-TLS without an Easy-RSA CA Certificate Authority.
      * Use 'no-ca' to build self-signed certificates and fingerprints.

      If you have upgraded an old copy of Easy-TLS to the latest version
      then it may be possible to use 'easytls upgrade' or 'easytls rehash'
      to establish a functioning easytls data directory." ;;
		build|inline|remove|script|selfsign) text="
  Inter-active menus to build, inline, remove
  and configure various Easy-TLS files" ;;
		bta|build-tls-auth) text="
  build-tls-auth
      Create an OpenVPN TLS auth PSK (tls-auth.key)" ;;
		btc|build-tls-crypt) text="
  build-tls-crypt
      Create an OpenVPN TLS crypt PSK (tls-crypt.key)" ;;
		btc2s|btv2s|btcv2s|build-tls-crypt-v2-server) text="
  build-tls-crypt-v2-server <server_filename_base>
      Create an OpenVPN TLS crypt V2 Server key" ;;
		btc2c|btv2c|btcv2c|build-tls-crypt-v2-client)
		EASYTLS_github="https://github.com/TinCanTech/easy-tls/wiki"
		EASYTLS_url="15-=-Multiple-TLS-Crypt-V2-Keys-per-X509-Certificate"
		text="
  build-tls-crypt-v2-client
      <server_filename_base> <client_filename_base> <HW-ADDR> <HW-ADDR>

      Create an OpenVPN TLS crypt V2 Client key

      The metadata can also be used to specify Hardware Addresses which this
      key is allowed to be used from.  Use easytls-cryptv2-client-connect.sh
      to verify the HW-ADDR.

      Note: ALL scripts are required to successfully verify Hardware.

      Each X509 Client can have multiple TLS-Crypt-V2 keys by using option:
      -k|--subkey-name=<NAME>

      This allows an unlimited number of keys, see the following wiki article
      for further details:
      ${EASYTLS_github}/${EASYTLS_url}" ;;
		bc2gs|btc2gs|btv2gs|btcv2gs|build-tls-crypt-v2-group-server)
			text="
  build-tls-crypt-v2-group-server <Server_Group_Name>
      The Server Group name is simply a name for the key file." ;;
		bc2gc|btc2gc|btv2gc|btcv2gc|build-tls-crypt-v2-group-client)
			text="
  build-tls-crypt-v2-group-client <Server_Group_Name> <Client_Group_Name>
      The Client Group name is simply a name for the key file." ;;
		rt|rtk|remove-tlskey) text="
  remove-tlskey <client_filename_base>
      Remove a TLS-Crypt-V2 Client key and update the tlskey-index." ;;
		rgt|rgk|rgtk|remove-group-tlskey) text="
  remove-group-tlskey <Group-Key-Name>
      Remove a TLS-Crypt-V2 GROUP Client key and update the tlskey-index." ;;
		sid|save-id) text="
  save-id [ no options ]
      Save the CA-Identity to easytls/easytls-ca-identity.txt
      CA-Identity is the Hex ONLY value of the CA finger print.
      This can then by used by easytls-cryptv2-verify.sh as a time-saver
      by not needing to load OpenSSL to generate the CA fingerprint.
      Combining this with 'easytls-cryptv2-verify.sh --verify-via-index',
      OpenSSL binary never needs to be loaded by easytls-cryptv2-verify.sh
      See EasyTLS Howto for an example." ;;
		s|status) text="
  status [ cmd-opts ]
      Compare Easy-RSA certificate(s) to Easy-TLS inline(s) status"
			opts="
	val|valid   - List only Easy-RSA valid certificates
	rev|revoked - List only Easy-RSA revoked certificates
	inl|inline  - List only Easy-TLS inline files
	inv|invalid - List only Easy-TLS invalid inline files
	ren|renewed - List only Easy-RSA renewed certificates
	tls|tlskeys - List only Easy-TLS known TLS keys" ;;
		ita|inline-tls-auth) text="
  inline-tls-auth <filename_base> <key_direction> [ cmd-opts ]
      Create a complete OpenVPN node package from Easy-RSA and Easy-TLS files
      for VPN node <filename_base> using the Easy-TLS TLS auth file
      <key_direction> '0' or '1' (key-direction is mandatory)"
			opts="
        no-key  - do not require an x509 key (default: key is required)
        add-dh  - inline Diffie-Hellman parameters file (Server only)
                  Default file is easyrsa3/pki/dh.pem
                  To specify an alternative file use the --dh option" ;;
		itc|inline-tls-crypt) text="
  inline-tls-crypt <filename_base> [ cmd-opts ]
      Create a complete OpenVPN node package from Easy-RSA and Easy-TLS files
      for VPN node <filename_base> using the Easy-TLS TLS crypt file"
			opts="
        no-key  - do not require an x509 key (default: key is required)
        add-dh  - inline Diffie-Hellman parameters file (Server only)
                  Default file is easyrsa3/pki/dh.pem
                  To specify an alternative file use the --dh option" ;;
		itc2|itv2|itcv2|inline-tls-crypt-v2) text="
  inline-tls-crypt-v2 <filename_base> [ cmd-opts ]
      Create a complete OpenVPN node package from Easy-RSA and Easy-TLS files
      for VPN node <filename_base> using the Easy-TLS TLS crypt v2 file"
			opts="
        no-key  - do not require an x509 key (default: key is required)
        add-dh  - inline Diffie-Hellman parameters file (Server only)
                  Default file is easyrsa3/pki/dh.pem
                  To specify an alternative file use the --dh option
        no-md   - Do not add public metadata details to inline file
        add-hw  - Include hardware address in metadata details" ;;
		ic2gc|itv2gc|itcv2gc|inline-tls-crypt-v2-group-client) text="
  inline-tls-crypt-v2-group-client
    <client_common_name> <client_group_name> [ cmd-opts ]
      Create a complete OpenVPN node package from Easy-RSA and Easy-TLS files
      for VPN node <client_common_name> using the Easy-TLS GROUP key file"
			opts="
        no-key  - do not require an x509 key (default: key is required)
        add-dh  - inline Diffie-Hellman parameters file (Server only)
                  Default file is easyrsa3/pki/dh.pem
                  To specify an alternative file use the --dh option
        no-md   - Do not add public metadata details to inline file
        add-hw  - Include hardware address in metadata details" ;;
		ri|ril|remove-inline) text="
  remove-inline <filename_base>
      Delete <filename_base>.inline and update the inline-index
      When an Easy-RSA certificate is revoked then the inline file is invalid" ;;
		rgi|rgil|remove-group-inline) text="
  remove-group-inline <Client-Name> <Group-Name>
      Delete <Client-Name>-<Group-Name>.inline and update the inline-index" ;;
		is|inline-show) text="
  inline-show <filename_base>
      Copy <filename_base>.inline to stdout" ;;
		inline-index-rebuild) text="
  inline-index-rebuild
      Rebuild easytls-index.txt
      If you need to do this then you may have found a bug, please
      raise an issue https://github.com/TinCanTech/easy-tls/issues" ;;
		ix|inline-expire) text="
  inline-expire <filename_base>
      Display inline expiry Date for <filename_base>"
			opts="
        If no <filename_base> is given then list all inline expiry Dates" ;;
		cx|cert-expire) text="
  cert-expire <filename_base> | <ca>
      Display certificate expiry Date for <filename_base>"
			opts="
        If <filename_base> is 'ca' then show ca.crt expiry
        If no <filename_base> is given then list all certificate expiry Dates" ;;
		d|disable) text="
  d|disable <filename_base>
      Add serial number for <filename_base> to the disabled list
      for immediate use by the easytls-cryptv2-verify.sh script.
      This also supports --sub-key-name for clients with multiple keys.
      If no <filename_base> is given then show the disabled-list." ;;
		e|enable) text="
  e|enable <filename_base>
      Remove serial number for <filename_base> from the disabled list
      for immediate use by the easytls-cryptv2-verify.sh script.
      This also supports --sub-key-name for clients with multiple keys.
      If no <filename_base> is given then show the disabled-list." ;;
		rehash) text="
  rehash
      Rehash the master-hash if the current hash is corrupted.

      These test commands also exist:
      gmh|generate-master-hash - Generate your current Master Hash.
      vmh|verify-master-hash   - Verify your current Master Hash.
      smh|save-master-hash     - Save your current Master Hash." ;;
		sss|self-sign-server) text="
  self-sign-server
       Build a self signed server certificate and key.
       Also see 'help options' for: -r|--ss-peer-fingerprint=<CommonName>" ;;
		ssc|self-sign-client) text="
  self-sign-client
       Build a self signed client certificate and key.
       Also see 'help options' for: -r|--ss-peer-fingerprint=<CommonName>" ;;
		upgrade) text="
  upgrade
       To upgrade from an older version of Easy-TLS which did not create
       the required folders and files. Very limited usage." ;;
		options)
			opt_usage ;;
		abb)
			opt_abbreviations ;;
		cf|cfg|config)
			opt_config ;;
		ver|version) text="
  Show version information." ;;
		"")	usage ;;
		*)	text="
  Unknown command: '${1}' (try without commands for a list of commands)" ;;
	esac

	# display the help text
	[ -z "${text}" ] || print "${text}"
	[ -z "${opts}" ] || print "
      cmd-opts is an optional set of command options from this list:
${opts}"
	easytls_verbose
} # => cmd_help()

# Options usage
opt_usage() {
	print "
Easy-TLS Global Option Flags

The following options may be provided before the command. Options specified
at runtime override env-vars and any 'vars' file in use. Unless noted,
non-empty values to options are mandatory.

General options:

-V                  Version
--batch             Set automatic (no-prompts when possible) mode.
-v|--verbose        Verbose output.
-s|--silent         Silence all message output except prompts.
-p|--pki-dir=<DIR>  Declare the EasyRSA PKI directory.
--vars='FILE'       Define a specific 'vars' file to use for Easy-RSA config.
--dh='FILE'         Define an alternate Diffie-Hellman parameters file.

-n|--no-auto-check  For performance you can disable auto-check.

-y|--why-disable-file-hash
                    To temporarily disable file hash verification.

-g|--custom-group=<GROUP-NAME>

      <CUSTOM-GROUP> is an optional single word which will be used
      in .inline files and TLS-Crypt-V2 client key metadata to identify
      the group to which this TLS-Crypt-V2 client key belongs.
      Once set and used, the <CUSTOM-GROUP> must not be changed.

      The <CUSTOM-GROUP> is also matched in easytls-cryptv2-verify.sh
      by using the command line switch --c|custom-group=XYZ

-k|--subkey|--sub-key-name=<SUBKEY-NAME>

      This allows for one single X509 client certificate to have multiple
      TLS-Crypt-V2 client keys associated with it.  For example, the same
      X509 certificate can be used from different locations with unique
      TLS-Crypt-V2 client keys.

-a|--ss-age=<AGE>   Self-signed certificate age. (Default: 3650 days)
-w|--ss-password    Prompt for a password to encrypt a self-signed key.
-c|--ss-eccurve=<CURVE_NAME>
                    Specify an alternate Elliptic Curve for a self-signed key.
                    (Default: secp384r1)

-r|--ss-peer-fingerprint=<CommonName>

      When inlining a self-signed client certificate, specify the commonName
      of the server certificate to share fingerprints of each node.
      The fingerprint of the server will be added to the client inline file
      and the fingerprint of the client will be added to the server inline file.

-i|--inline         When building a TLS Crypt V2 key, also build the Inline file.
              (Fix: This only accepts default options for inline-tls-crypt-v2)

-t|--tmp-dir=<DIR>     Temp directory where server-scripts write data.
                       Default: *nix /tmp/easytls
                                Windows C:/Windows/Temp/easytls
-b|--base-dir=<DIR>    Path to OpenVPN base directory. (Windows Only)
                       Default: C:/Progra~1/OpenVPN
-o|--ovpnbin-dir=<DIR> Path to OpenVPN bin directory. (Windows Only)
                       Default: C:/Progra~1/OpenVPN/bin
-e|--ersabin-dir=<DIR> Path to Easy-RSA3 bin directory. (Windows Only)
                       Default: C:/Progra~1/Openvpn/easy-rsa/bin
"
} # => opt_usage()

# Option abbreviations
opt_abbreviations () {
	print "
Easy-TLS abbreviations:

  config                           - cf | cfg
  init-tls                         - init
  build-tls-auth                   - bta
  build-tls-crypt                  - btc
  build-tls-crypt-v2-server        - bc2s | btc2s | btv2s | btcv2s
  build-tls-crypt-v2-client        - bc2c | btc2c | btv2c | btcv2c
  build-tls-crypt-v2-group-server  - bc2gs | btc2gs | btcv2gs
  build-tls-crypt-v2-group-client  - bc2gc | btc2gc | btcv2gc

  remove-tlskey                    - rt | rk | rtk
  save-id                          - sid

  status                           - s
	Easy-RSA valid certificates      - val | valid
	Easy-RSA revoked certificates    - rev | revoked
	Easy-TLS inline files            - inl | inline
	Easy-TLS invalid inline files    - inv | invalid
	Easy-RSA renewed certificates    - ren | renewed
	Easy-TLS known TLS keys          - tls | tlskeys

  inline-tls-auth                  - ita
  inline-tls-crypt                 - itc
  inline-tls-crypt-v2              - ic2 | itc2 | itv2 | itcv2
  inline-tls-crypt-v2-group-client - ic2gc | itc2gc | itcv2gc

  remove-inline                    - ri | ril
  remove-group-inline              - rgi | rgil
  inline-show                      - is
  inline-expire                    - ix
  cert-expire                      - cx
  disable                          - d
  enable                           - e
  rehash                           - No abbreviation

  self-sign-server                 - sss
  self-sign-client                 - ssc
"
} # => opt_abbreviations ()

# Option config
opt_config () {
	print "
Easy-TLS configurable options (Abbreviation|full option):

  cg|custom.group NAME
     Save the Custom Group NAME - NAME must be a single contiguous word.

     This group can be changed.

     Using easytls-cryptv2-verify.sh in your openvpn server, you can
     configure multiple group names to match. eg. 'Home Office Remote'

  td|tmp-dir
     Set server scripts temporary directory

  ac|auto.check on|off
     Always run auto-check
     Toggle auto.check on|off.

  co|custom.openvpn '/full/path/to/openvpn(.exe)'
     Save your custom openvpn binary location.
     EG: /usr/local/bin/openvpn
         Quotes are not required.
     EG: 'C:/Program Files/OpenVPN/mybin/openvpn.exe'
         Quotes are required for spaces.
         Back-slash is not support, use Forward-slash '/' ONLY.

  im|inline.metadata on|off
     Add metadata to inline file
     Toggle inline-file metadata on|off.

  ih|inline.hardware on|off
     Include metadata hardware-addresses in metadata for inline file
     Toggle inline-file hardware-address metadata on|off.

  addition (No short form)
     Add arbitrary item to config
     This only adds the <label = > item to the config.
     The item must then be configured.
     This allows for future additions to config without the need to hack.

  deletion (No short form)
     delete an item from config
     Should the need arise, items can now be deleted also.
"
} # => opt_config ()



############################################################################
#
# EASYTLS TO EASYRSA3 COMPATIBILITY Section
#

# Wrapper around 'printf' - clobber 'print' since it's not POSIX anyway
print () {
	[ -z "${EASYTLS_SILENT}" ] || return 0
	"${EASYTLS_PRINTF}" "%s\n" "${*}"
} # => print ()

# Exit fatally with a message to stderr
# present even with EASYTLS_BATCH as these are fatal problems
die () {
	unset -v EASYTLS_SILENT
	print
	error_msg
	print "Error: ${1}" 1>&2
	print
	[ -n "${help_note}" ] && print "Note: ${help_note}" && print
	easytls_version
	exit "${2:-1}"
} # => die ()

# Specific error messages from sub-functions which do not use die()
error_msg () {
	if [ -n "${1}" ]; then
		error_log="${error_log:-ERROR:}
${1}"
	else
		print "Error log: ${error_log}"
		print
	fi
} # => error_msg ()

# Missing files
missing_file () {
	die "Missing file: ${1}"
} # => missing_file ()

# Fatal errors prior to deps
fatal_opt () {
	if [ -z "${1}" ]; then
		# If fatal_msg is not set then ok
		[ -n "${fatal_msg}" ] || return 0
	else
		# Add the message to fatal_msg and ok
		fatal_msg="${fatal_msg}
${1}"
		return 0
	fi
	# fatal_msg is set
	die "${fatal_msg}"
} # => fatal_opt ()

# remove temp files and do terminal cleanups
cleanup () {
	"${EASYTLS_RM}" -f "${EASYTLS_SSL_CONF}" \
		"${EASYTLS_TEMP_LIST}" "${EASYTLS_TEMP_RECORD}"

	if [ -d "${EASYTLS_TEMP_LOCK}" ]; then
		"${EASYTLS_RM}" -r "${EASYTLS_TEMP_LOCK}"
		easytls_verbose "Lock released"
	fi

	if [ -n "${EASYTLS_FOR_WINDOWS}" ]; then
		# shellcheck disable=SC3040
		set -o echo
	else
		[ -t 1 ] && stty echo
	fi
	[ -n "${EASYTLS_SILENT}" ] || echo "" # just to get a clean line
} # => cleanup ()

# non-fatal warning output
warn () {
	[ -z "${EASYTLS_SILENT}" ] || return 0
	print "
WARNING:
  ${1}
" 1>&2
} # => warn ()

# informational notices to stdout
notice () {
	[ -z "${EASYTLS_QUIET}" ] || return 0
	[ -z "${EASYTLS_SILENT}" ] || return 0
	print "
${1}"
} # => notice ()

# intent confirmation helper func
# returns without prompting in EASYTLS_BATCH
confirm () {
	[ -z "${require_batch}" ] || return 0
	[ -z "${EASYTLS_BATCH}" ] || return 0
	prompt="${1}"
	value="${2}"
	msg="${3}"
	input=""
	print "
${msg}

  Type the word '${value}' to continue, or any other input to abort."
	"${EASYTLS_PRINTF}" '\n%s' "  ${prompt}"
	read -r input
	if [ "${input}" = "${value}" ]; then
		"${EASYTLS_PRINTF}" "\n"
		return 0
	fi
	notice "Aborting without confirmation."
	exit 9
} # => confirm ()

# Check for defined EASYRSA_PKI
vars_source_check () {
	[ -n "${EASYRSA_PKI}" ] || die "EASYRSA_PKI env-var undefined"
} # => vars_source_check ()

# Basic sanity-check of PKI init and complain if missing
verify_pki_init () {
	# check that the pki dir exists
	vars_source_check
	[ -d "${EASYRSA_PKI}" ] || {
			error_msg "Easy-RSA has not been initialised."
			error_msg "Easy-TLS requires an Easy-RSA CA"
			error_msg "Otherwise, use Easy-TLS in No-CA mode"
			error_msg "$(cmd_help init-tls||:)"
			return 1
			# die "verify_pki_init - vars_source_check"
			}

	# verify expected dirs present:
	for i in private reqs; do
		[ -d "${EASYRSA_PKI}/${i}" ]  || {
			error_msg "Easy-RSA has not been initialised."
			error_msg "Easy-TLS requires an Easy-RSA CA"
			error_msg "Otherwise, use Easy-TLS in No-CA mode"
			error_msg "$(cmd_help init-tls||:)"
			return 1
			# die "verify_pki_init - private reqs"
			}
	done
} # => verify_pki_init ()

# Verify core CA files present
verify_ca_init () {
	help_note="Run without commands for usage and command help."

	# First check the PKI has been initialized
	verify_pki_init

	# Verify expected files are present. Allow files to be regular files
	# (or symlinks), but also pipes, for flexibility with ca.key
	for i in serial index.txt index.txt.attr ca.crt private/ca.key; do
		if [ ! -f "${EASYRSA_PKI}/${i}" ] && [ ! -p "${EASYRSA_PKI}/${i}" ]; then
			#[ "$1" = "test" ] && return 1
			die "
Easy-TLS requires that you have built your EASY-RSA CA.

  Easy-RSA error:

Missing expected CA file: ${i} (perhaps you need to run build-ca?)
${help_note}"
		fi
	done

	# When operating in 'test' mode, return success.
	# test callers don't care about CA-specific dir structure
	#[ "$1" = "test" ] && return 0

	# verify expected CA-specific dirs:
	for i in issued certs_by_serial
	do
		[ -d "${EASYRSA_PKI}/${i}" ] || die "
Easy-TLS requires that you have built your EASY-RSA CA.

  Easy-RSA error:

Missing expected CA dir: ${i} (perhaps you need to run build-ca?)
${help_note}"
	done
	unset -v help_note
} # => verify_ca_init ()



############################################################################
#
# EASYTLS MANAGEMENT Section
#

# Verbose messages
easytls_verbose () {
	[ -n "${EASYTLS_VERBOSE}" ] || return 0
	[ -z "${EASYTLS_SILENT}" ] || return 0
	[ -z "${flash_config}" ] || return 0
	[ -z "${EASYTLS_verboff}" ] || return 0
	verbose_message="${*}"
	[ -n "${verbose_message}" ] || { print ""; return 0; }
	"${EASYTLS_PRINTF}" '%s\n' "* ${verbose_message}"
} # => easytls_verbose ()

# Configurable options
easytls_config () {
	# Verify config-file and hash has already been done by config_use()
	case "${1}" in
	addition)
		cfg_opt="${2}"
		[ -n "${cfg_opt}" ] || { error_msg "Required: option"; return 1; }
		config_addition && return 0
		error_msg "Failed to add: ${cfg_opt}"
		return 1
	;;
	deletion)
		cfg_opt="${2}"
		[ -n "${cfg_opt}" ] || { error_msg "Required: option"; return 1; }
		config_deletion && return 0
		error_msg "Failed to delete: ${cfg_opt}"
		return 1
	;;
	nc|no.ca)			cfg_opt="no.ca"; cfg_val="${2}" ;;
	ha|hash.algorithm)	cfg_opt="hash.algorithm"; cfg_val="${2}" ;;
	td|tmp.dir)			cfg_opt="tmp.dir"; cfg_val="${2}" ;;
	ac|auto.check)		cfg_opt="auto.check"; cfg_val="${2}" ;;
	cg|custom.group)	cfg_opt="custom.group"; cfg_val="${2}" ;;
	id|ca.id)			cfg_opt="ca.id"; cfg_val="${2}" ;;
	co|custom.openvpn)	cfg_opt="custom.openvpn"; cfg_val="${2}" ;;
	im|inline.metadata)	cfg_opt="inline.metadata"; cfg_val="${2}" ;;
	ih|inline.hardware)	cfg_opt="inline.hardware"; cfg_val="${2}" ;;
	status)				cfg_opt="status"; cfg_val="${2}" ;;
	test.bool)			cfg_opt="test.bool"; cfg_val="${2}" ;;
	'')
		easytls_verbose
		"${EASYTLS_PRINTF}" "%s\n" "easytls = ${EASYTLS_VERSION}"
		"${EASYTLS_GREP}" -v 'status' "${EASYTLS_CONFIG_FILE}"
		easytls_verbose
		skip_master_hash=1
		return 0
	;;
	*)
		error_msg "Unknown option: ${1}"
		return 1
	esac

	case "${cfg_opt}" in
	test.bool)
		case "${cfg_val}" in
		0|1)	: ;; # ok
		*)
			help_note="Supported options:  0 | 1"
			die "Unsupported value for ${cfg_opt}: ${cfg_val}"
		esac
	;;
	no.ca)
		help_note="See help for init-tls"
		die "To change No-CA mode you must create a new Easy-TLS PKI"
	;;
	tmp.dir)
		# Test for dir
		[ -d "${cfg_val}" ] || missing_file "${cfg_val}"
	;;
	auto.check|inline.metadata|inline.hardware)
		case "${cfg_val}" in
		on|off)	 ;; # ok
		*)
			help_note="Supported options:  on | off"
			die "Unsupported value for ${cfg_opt}: ${cfg_val}"
		esac
	;;
	custom.group)
		# Cannot be empty
		[ -n "${cfg_val}" ] || die "custom.group cannot be empty"
		# spaces ?
		test_val="${cfg_val%% *}"
		[ "${cfg_val}" = "${test_val}" ] || {
			help_note="custom.group does not support spaces."
			die "Unsupported value for ${cfg_opt}: ${cfg_val}"
			}

		confirm "Change your Custom-Group: " "yes" \
"NOTICE:

* If you change your custom group here then you will need to use
  ENABLE_MULTI_CUSTOM_G=1 in your easytls-cryptv2-verify.vars file.
  And also insert all the custom groups into LOCAL_CUSTOM_G variable."
	;;
	custom.openvpn)
		if [ -n "${cfg_val}" ]; then
			# Test for file
			[ -f "${cfg_val}" ] || missing_file "${cfg_val}"
		fi
	;;
	ca.id)
		if [ -n "${EASYTLS_MASTER_ID}" ]; then
			help_note="To change your CA-ID you must create a new Easy-RSA CA"
			die "The CA-ID cannot be changed"
		fi

		if [ -z "${save_id_authorized}" ]; then
			die "Please use 'easytls save-id' to configure your CA-ID"
		fi
	;;
	hash.algorithm)
		help_note="See help for init-tls"
		die "To change your HASH you must create a new Easy-TLS PKI"
	;;
	status) [ -n "${flash_config}" ] || return 0 ;;
	*)	die "Unknown option: ${cfg_opt}"
	esac

	# Write config
	config_update || die "Error updating config."
	#config_save_hash || die "Error hashing config."
	config_use || die "config_use (updated) - Error: ${?}"
} # => easytls_config ()

# Update config
config_update () {
	# remove old_record
	old_record="^${cfg_opt} = .*\$"
	if universal_update del "${EASYTLS_CONFIG_FILE}" "${old_record}"; then
		: # ok
	else
		return 1
	fi

	# add new_record
	new_record="${cfg_opt} = ${cfg_val}"
	if universal_update add "${EASYTLS_CONFIG_FILE}" "${new_record}"; then
		: # ok
	else
		return 1
	fi

	easytls_verbose
	easytls_verbose "config-file updated: ${cfg_opt} = ${cfg_val}"
	easytls_verbose
	update_master_hash=1
} # => config_update ()

# Use config
config_use () {
	# Verify once for config and then again later for other commands
	# Don't load config if it does not exist
		[ -f "${EASYTLS_CONFIG_FILE}" ] || return 0

	# Cannot use IFS because Windows/sh/read needs to recognise CRLF as well
	# It is simplest to rely on Windows/sh/read default which does CRLF or NL
	# Any writes to config and Windows/sed converts all CRLF to NL anyway
	unset -v config_error
	while read -r cfg_opt cfg_equ cfg_val; do
		# May as well check it for shellcheck
		if [ "${cfg_equ}" != "=" ]; then
			error_msg "Config error: Require '='"
			config_error=2
		fi

		# These settings do not change command line
		case "${cfg_opt}" in
		test.bool) : ;;
		auto.check)
			case "${cfg_val}" in
			off)	set_var AUTO_CHECK_DISABLED 1 ;;
			on)		: ;;
			*)
				error_msg "Config error: Invalid value: ${cfg_val}"
				config_error=3
			esac
		;;
		inline.metadata)
			case "${cfg_val}" in
			off)	set_var no_metadata 1 ;;
			on)		: ;;
			*)
				error_msg "Config error: Invalid value: ${cfg_val}"
				config_error=4
			esac
		;;
		inline.hardware)
			case "${cfg_val}" in
			off)	: ;;
			on)		set_var add_hardware 1 ;;
			*)
				error_msg "Config error: Invalid value: ${cfg_val}"
				config_error=5
			esac
		;;
		custom.group)
			[ -z "${cfg_val}" ] || set_var TLSKEY_CUSTOM_GRP "${cfg_val}"
			set_var TLSKEY_CUSTOM_GRP "EASYTLS"
		;;
		custom.openvpn)
			[ -z "${cfg_val}" ] || set_var EASYTLS_OPENVPN "${cfg_val}"
			set_var EASYTLS_OPENVPN "openvpn"
		;;
		tmp.dir)
			[ -z "${cfg_val}" ] || set_var EASYTLS_tmp_dir "${cfg_val}"
			;;
		no.ca)
			# This is set explicitly here to protect config
			[ -z "${cfg_val}" ] || export EASYTLS_NO_CA="${cfg_val}"
		;;
		hash.algorithm)
			# This is set explicitly here to protect config
			[ -z "${cfg_val}" ] || export EASYTLS_HASH_ALGO="${cfg_val}"
		;;
		ca.id)
			# This is set explicitly here to protect config
			export EASYTLS_MASTER_ID="${cfg_val}"
		;;
		status)
			EASYTLS_STATUS="${cfg_val}"
			unset -v flash_config
		;;
		*)	error_msg "Config error: Ignored option: ${cfg_opt}"
		esac
	done < "${EASYTLS_CONFIG_FILE}"
	[ -z "${config_error}" ] || return "${config_error}"

	# Set default hash.algorithm
	EASYTLS_HASH_ALGO="${EASYTLS_HASH_ALGO:-SHA256}"

	# Set fixed hash
	Zero_x8=00000000
	Zero_x16="${Zero_x8}${Zero_x8}"

	case "${EASYTLS_HASH_ALGO}" in
	SHA1)
	fixed_hash=1111111111111111111111111111111111111111
	fixed_length=40
	;;
	SHA256)
	fixed_hash=2222222222222222222222222222222222222222222222222222222222222222
	fixed_length=64
	;;
	*)
		error_msg "config_use - Unknown algorithm: ${EASYTLS_HASH_ALGO}"
		return 1
	esac

	# Group mode is not tied to a CA or Certificates
	# Group server TLS-CV2 Key is free to roam anywhere
	# Group client TLS-CV2 Key must follow the the server key which built it
	group_mode_fixed_ca_id="${Zero_x16}${Zero_x16}${Zero_x16}${Zero_x16}"

	# generate forbidden_hash (empty input)
	if easytls_ssl_generate_empty_hash; then
		forbidden_hash="${empty_hash}"
		unset -v empty_hash
	else
		error_msg "config_use - easytls_ssl_generate_empty_hash"
		return 1
	fi

	# Fixed TLS key serial
	fixed_tls_auth_serial=7A01
	fixed_tls_cryptv1_serial=7C01

	# maintenance
	config_version || { error_msg "config_use - config_version"; return 1; }

	easytls_verbose "config_use OK"
} # => config_use ()

# verify config file hash
config_verify_hash () {
	# DISABLED_DELETE
	die "DISABLE: config_verify_hash"
	[ -z "${config_verify_hash_block}" ] || \
		die "config verify hash must only run once"
	request_fixed_hash=1
	generate_and_match_valid_hash \
		"${EASYTLS_CONFIG_FILE}" "${EASYTLS_CONFIG_HASH}" || {
			error_msg "config_verify_hash - generate_and_match_valid_hash"
			return 1
			}
	unset -v request_fixed_hash
	easytls_verbose "config_verify_hash OK"
	config_verify_hash_block=1
} # => config_save_hash ()

# Hash config-file
config_save_hash () {
	# DISABLED_DELETE
	die "DISABLE: config_save_hash"
	[ -z "${config_save_hash_block}" ] || \
		die "config save hash must only run once"
	request_fixed_hash=1
	generate_and_save_file_hash \
		"${EASYTLS_CONFIG_FILE}" "${EASYTLS_CONFIG_HASH}" || {
			error_msg "config_save_hash - generate_and_save_file_hash"
			return 1
			}
	unset -v request_fixed_hash
	easytls_verbose "config-file hash save OK"
	update_master_hash=1
	config_save_hash_block=1
} # => config_save_hash ()

# Add a new option to config
config_addition () {
	# Only allow alpha caracters and '.'
	if "${EASYTLS_PRINTF}" "%s" "${cfg_opt}" | \
		"${EASYTLS_GREP}" -q '[^abcdefghijklmnopqrstuvwxyz\.]'
	then
		error_msg "config_addition - label error: ${cfg_opt}"
		return 1
	fi

	# Find the pattern - Fail if found
	if "${EASYTLS_GREP}" -q "^${cfg_opt}[[:blank:]]=[[:blank:]].*$" \
		"${EASYTLS_CONFIG_FILE}"
	then
		error_msg "config_addition - duplication error: ${cfg_opt}"
		return 1
	fi

	# Add the new pattern
	"${EASYTLS_CP}" "${EASYTLS_CONFIG_FILE}" "${EASYTLS_TEMP_LIST}"
	"${EASYTLS_PRINTF}" "%s\n" "${cfg_opt} = " > "${EASYTLS_TEMP_RECORD}"
	"${EASYTLS_CAT}" "${EASYTLS_TEMP_LIST}" "${EASYTLS_TEMP_RECORD}" > \
		"${EASYTLS_CONFIG_FILE}" || {
			error_msg "config_addition - addition error: ${cfg_opt}"
			return 1
			}

	# Save hash
	config_save_hash || die "Error hashing config."
	notice "New option added to config: ${cfg_opt}"
} # => config_addition ()

# Delete an option from config
config_deletion () {
	# Verify the Config hash
	generate_and_match_valid_hash \
		"${EASYTLS_CONFIG_FILE}" "${EASYTLS_CONFIG_HASH}" || {
			error_msg "config_deletion hash fail"
			return 1
			}

	# Find the pattern
	"${EASYTLS_GREP}" -q "^${cfg_opt}[[:blank:]]=[[:blank:]].*$" \
		"${EASYTLS_CONFIG_FILE}" || {
			error_msg "config_deletion - verify error: ${cfg_opt}"
			return 1
			}

	# Remove the pattern
	"${EASYTLS_SED}" -i \
		-e "/^${cfg_opt}[[:blank:]]=[[:blank:]].*$/d" \
		"${EASYTLS_CONFIG_FILE}" || {
			error_msg "config_deletion - delete error: ${cfg_opt}"
			return 1
			}

	# Save hash
	config_save_hash || die "Error hashing config."
	notice "Option deleted from config: ${cfg_opt}"
} # => config_deletion ()

# Maintain config to Easy-TLS version
config_version () {
	flash_config=1
	# Add status
	if "${EASYTLS_GREP}" -q "status = " "${EASYTLS_CONFIG_FILE}"; then
		:
	else
		cfg_opt="status"
		cfg_val=2
		config_addition
		config_update
		config_save_hash || return 1
		EASYTLS_STATUS=2
		update_master_hash=1
	fi

	# Add tmp.dir
	if "${EASYTLS_GREP}" -q "tmp.dir = " "${EASYTLS_CONFIG_FILE}"; then
		:
	else
		cfg_opt="tmp.dir"
		cfg_val=""
		config_addition
		config_save_hash || return 1
		update_master_hash=1
	fi
	unset -v flash_config cfg_opt cfg_val
} # => config_version ()

# Verify Openvpn is available
verify_openvpn () {
	# extract openvpn version
	openvpn_full_version="$("${EASYTLS_OPENVPN}" --version)"
	openvpn_version="${openvpn_full_version%% *}"
	case "${openvpn_version}" in
	OpenVPN) : ;; # ok
	*) die "Missing or invalid OpenVPN: ${openvpn_version}"
	esac

	openvpn_version="${openvpn_full_version#OpenVPN }"
	openvpn_version="${openvpn_version%% *}"
	openvpn_version="${openvpn_version%_*}"
	[ "${#openvpn_version}" -lt 4 ] || openvpn_version="${openvpn_version%.*}"
	case "${openvpn_version}" in
	2.5|2.6) : ;; # ok
	2.4) : ;; # ok
	*) die "Unsupported OpenVPN version: ${openvpn_version}"
	esac
} # => verify_openvpn ()

# Verify Openssl is available
verify_openssl () {
	# extract openssl version
	openssl_full_version="$("${EASYRSA_OPENSSL}" version)"
	openssl_version="${openssl_full_version#* }"
	openssl_name="${openssl_full_version%% *}"
	case "${openssl_name}" in
	OpenSSL) : ;; # ok
	*) die "Missing or invalid OpenSSL: ${openssl_version}"
	esac

	openssl_version="${openssl_version%% *}"
	easytls_verbose "openssl_version: ${openssl_version}"
	[ "${#openssl_version}" -lt 4 ] || openssl_version="${openssl_version%.*}"
	case "${openssl_version}" in
	3.?) openssl_version=3 ;;
	1.1) openssl_version=1 ;;
	*) die "Unsupported OpenSSL version: ${openssl_version}"
	esac
} # => verify_openssl ()

# Verify TLS has been initialised
verify_tls_init () {
	if	[ -d "${EASYTLS_PKI}" ] && \
		[ -f "${EASYTLS_CONFIG_FILE}" ] && \
		[ -f "${EASYTLS_INLINE_INDEX}" ] && \
		[ -f "${EASYTLS_TLSKEY_INDEX}" ] && \
		[ -f "${EASYTLS_DISABLED_LIST}" ] && \
		[ -f "${EASYTLS_FASTER_HASH}" ]
	then
		easytls_verbose "verify_tls_init OK"
	else
		error_msg "verify_tls_init fail"
		easytls_verbose
		return 1
	fi
} # => verify_tls_init ()

# init-tls backend:
init_tls () {
	while :; do
		case "${1}" in
			sha1|SHA1)
				EASYTLS_HASH_ALGO="SHA1" ;;
			nc|noca|no-ca)
				EASYTLS_NO_CA=1 ;;
			'')
				# Ignore empty values
				: ;;
			*)
				die "Unknown command option: '${1}'"
		esac
		[ $# = 0 ] && break
		shift
	done

	# Easy-TLS requires that your Easy-RSA PKI is initialised
	if [ -z "${EASYTLS_NO_CA}" ]; then
		verify_pki_init || return 1
	fi

	# If EASYTLS_PKI exists, confirm before we remove
	# skipped with EASYTLS_BATCH
	if [ -d "${EASYTLS_PKI}" ]; then
		confirm "Confirm removal: " "yes" "
WARNING!!!

You are about to remove the EASYTLS_PKI at: ${EASYTLS_PKI}
and initialize a fresh TLS PKI here."
		# now remove it:
		"${EASYTLS_RM}" -rf "${EASYTLS_PKI}" || \
			die "Removal of TLS dir failed. Check/correct errors above"
	fi

	# Create Easy-TLS structure
	easytls_create_layout || {
		error_msg "init_tls - easytls_create_layout"
		return 1
		}

	# User notices
	notice "init-tls complete; you may now create TLS keys and .inline files.
  Your newly created TLS dir is:

    ${EASYTLS_PKI}"

	notice "To configure your Easy-TLS custom group now, use:
    'easytls config custom.group YOUR_GROUP'"

	notice "To configure your Easy-TLS temporary directory now, use:
    'easytls config tmp.dir YOUR_DIR'"

	print
	print "Recommended temporary directory setting:"
	if [ -n "${EASYTLS_FOR_WINDOWS}" ]; then
		print \
			"    Windows - ${host_drv}:/Windows/Temp (Use '/' NOT '\')"
	else
		print \
			"    *nix    - /var/easytls (Create with sudo)"
		print \
			"    systemd - /tmp (Use systemd unit file openvpn-server@.service)"
	fi
} # => init_tls ()

# Create Easy-TLS data files
easytls_create_layout () {
	# Explicitly set EASYTLS_HASH_ALGO here
	EASYTLS_HASH_ALGO="${EASYTLS_HASH_ALGO:-SHA256}"

	# Set fixed hash
	case "${EASYTLS_HASH_ALGO}" in
	SHA1)
	fixed_hash=1111111111111111111111111111111111111111
	fixed_length=40
	;;
	SHA256)
	fixed_hash=2222222222222222222222222222222222222222222222222222222222222222
	fixed_length=64
	;;
	*) die "Unknown algorithm: ${EASYTLS_HASH_ALGO}"
	esac

	# generate forbidden_hash (empty input)
	if easytls_ssl_generate_empty_hash; then
		forbidden_hash="${empty_hash}"
		unset -v empty_hash
	else
		error_msg "easytls_create_layout - easytls_ssl_generate_empty_hash"
		return 1
	fi

	# Get date
	full_date="$("${EASYTLS_DATE}" '+%s %Y/%m/%d-%H:%M:%S')" || \
		die "Failed to get date"
	local_date_ascii="${full_date##* }"
	local_time_unix="${full_date%% *}"
	head_date="${local_date_ascii}"

	# Create easytls dir in EasyRSA-PKI dir
	if [ ! -d "${EASYTLS_PKI}" ]; then
		"${EASYTLS_MKDIR}" -p "${EASYTLS_PKI}" || return 1
	fi

	# Create data dir in easytls dir
	if [ ! -d "${EASYTLS_DATA_DIR}" ]; then
		"${EASYTLS_MKDIR}" -p "${EASYTLS_DATA_DIR}" || return 1
	fi

	# Create metadata dir in easytls dir
	if [ ! -d "${EASYTLS_META_DATA_D}" ]; then
		"${EASYTLS_MKDIR}" -p "${EASYTLS_META_DATA_D}" || return 1
	fi

	# Create config-file
	if [ ! -f "${EASYTLS_CONFIG_FILE}" ]; then
	{
		"${EASYTLS_PRINTF}" '%s\n' "no.ca = ${EASYTLS_NO_CA}"
		"${EASYTLS_PRINTF}" '%s\n' "hash.algorithm = ${EASYTLS_HASH_ALGO}"
		"${EASYTLS_PRINTF}" '%s\n' "tmp.dir = "
		"${EASYTLS_PRINTF}" '%s\n' "ca.id = "
		"${EASYTLS_PRINTF}" '%s\n' "custom.group = "
		"${EASYTLS_PRINTF}" '%s\n' "auto.check = on"
		"${EASYTLS_PRINTF}" '%s\n' "inline.metadata = on"
		"${EASYTLS_PRINTF}" '%s\n' "inline.hardware = off"
		"${EASYTLS_PRINTF}" '%s\n' "custom.openvpn = "
		"${EASYTLS_PRINTF}" '%s\n' "status = 0"
	} > "${EASYTLS_CONFIG_FILE}" || {
			error_msg "easytls_create_layout - config-file"
			return 1
			}
	fi
	# Save HASH file
	"${EASYTLS_PRINTF}" '%s' "${fixed_hash}" > "${EASYTLS_CONFIG_HASH}" || {
			error_msg "easytls_create_layout - config-hash"
			return 1
			}

	# Create inline-index
	if [ ! -f "${EASYTLS_INLINE_INDEX}" ]; then
		head_text="# EasyTLS inline-index - Created: ${head_date}"
		field_names="inline-hash|inline-serial|CN|sub-key|tlskey-serial"
		head_text="${head_text} - ${field_names}"
		"${EASYTLS_PRINTF}" '%s\n' "${head_text}" > \
			"${EASYTLS_INLINE_INDEX}" || {
			error_msg "easytls_create_layout - inline-index"
			return 1
			}
	fi
	# Save HASH file
	"${EASYTLS_PRINTF}" '%s' "${fixed_hash}" > "${EASYTLS_INLINE_X_HASH}" || {
			error_msg "easytls_create_layout - inline-index-hash"
			return 1
			}

	# Create tlskey-index
	if [ ! -f "${EASYTLS_TLSKEY_INDEX}" ]; then
		head_text="# EasyTLS tlskey-index - Created: ${head_date}"
		field_names="tlskey-serial|cert-serial|CN|sub-key"
		head_text="${head_text} - ${field_names}"
		"${EASYTLS_PRINTF}" '%s\n' "${head_text}" > \
			"${EASYTLS_TLSKEY_INDEX}" || {
			error_msg "easytls_create_layout - tlskey-index"
			return 1
			}
	fi
	# Save HASH file
	"${EASYTLS_PRINTF}" '%s' "${fixed_hash}" > "${EASYTLS_KEY_X_HASH}" || {
			error_msg "easytls_create_layout - tlskey-index-hash"
			return 1
			}

	# Create disabled-list
	if [ ! -f "${EASYTLS_DISABLED_LIST}" ]; then
		head_text="# EastTLS disabled-list - Created: ${head_date}"
		"${EASYTLS_PRINTF}" '%s\n' "${head_text}" > \
			"${EASYTLS_DISABLED_LIST}" || {
			error_msg "easytls_create_layout - disabled-list"
			return 1
			}
	fi
	# Save HASH file
	"${EASYTLS_PRINTF}" '%s' "${fixed_hash}" > "${EASYTLS_DISABLED_HASH}" || {
			error_msg "easytls_create_layout - disabled-list-hash"
			return 1
			}

	# Auto-save the current EasyRSA CA-ID if available, warn if not
	if [ -z "${EASYTLS_NO_CA}" ]; then
		EASYTLS_INIT=1
		save_id || "${EASYTLS_PRINTF}" '%s\n\n' "      Failed to Save CA-ID."
		unset -v EASYTLS_INIT
	fi

	# Save HASH file
	update_master_hash=1
	"${EASYTLS_PRINTF}" '%s' "${fixed_hash}" > "${EASYTLS_FASTER_HASH}" || {
			error_msg "easytls_create_layout - master-hash"
			return 1
			}
} # => easytls_create_layout ()

# Rehash file hashes
easytls_rehash () {
	update_master_hash=1
	save_master_hash || return 1
	unset -v update_master_hash
	skip_master_hash=1
	print "
Rehash completed successfully.
"
} # => easytls_rehash ()

# Create missing files for old Easy-TLS
easytls_upgrade () {
	if verify_tls_init 1>/dev/null; then
		print "
Easy-TLS upgrade is not required, no change."
	else
		easytls_create_layout || return 1
		print "
Easy-TLS has been successfully upgraded.

Note:
* The upgrade CANNOT build new indexes from your inline files or TLS-Keys.
* The upgrade CANNOT extract metadata from your TLS-keys.
* To build your indexes and metadata files, you MUST create new TLS-Keys
  and inline files."
	fi
	EASYTLS_verboff=1
} # => easytls_upgrade ()

# Auto-check for invalid inline files
# Run on exit to avoid "chicken and egg" with init-tls
easytls_auto_check () {
	[ -z "${status_disabled_auto_check}" ] || return 0
	[ -z "${AUTO_CHECK_DISABLED}" ] || {
		easytls_verbose "auto-check disabled"
		return 0
		}

	easytls_verbose "auto-check"
	auto_check=1
	# ALL status failures MUST be FATAL in future

	# If inline-index does not exist then do not auto-check
	[ -f "${EASYTLS_INLINE_INDEX}" ] || return 0
	index_size="$("${EASYTLS_GREP}" -c '^.*$' "${EASYTLS_INLINE_INDEX}")"
	[ "${index_size}" -lt 50 ] || \
		warn "EasyTLS: 50+ .inline files found, use --disable-auto-check"

	# Check for revoked EasyRSA certs
	# which still have an inline file
	status invalid || die "inline_auto_check: status invalid error"
	[ "${revoked_mismatch_count}" -eq 0 ] || \
		warn "EasyTLS: Invalid .inline files found: Revoked certs."

	# Check serial number for renewed EasyRSA certs
	# which do not match inline files
	status renewed || die "inline_auto_check: status renewed error"
	[ "${renewed_mismatch_count}" -eq 0 ] || \
		warn "EasyTLS: Invalid .inline files found: Renewed certs."

	# Update status
	status_update
	easytls_verbose "Completed auto-check"
	unset -v auto_check
} # => easytls_auto_check ()

# universal update config/index/list
universal_update ()
{
	[ "$#" -eq 3 ] || return 1

	action="${1}"
	target="${2}"
	record="${3}"

	# Valid target
	[ -f "${target}" ] || missing_file "universal_update - ${target}"
	[ ! -f "${EASYTLS_TEMP_LIST}" ] || "${EASYTLS_RM}" -f "${EASYTLS_TEMP_LIST}"
	"${EASYTLS_CP}" "${target}" "${EASYTLS_TEMP_LIST}" || {
		error_msg "universal_update - copy target to temp-list"
		return 1
		}

	unset -v universal_update_ok

	# Action
	case "${action}" in
	add)
		if "${EASYTLS_GREP}" -q "${record}" "${EASYTLS_TEMP_LIST}"; then
			error_msg "universal_update - add - record exists"
		else
			# Add record
			if	{	"${EASYTLS_CAT}" "${EASYTLS_TEMP_LIST}"
					"${EASYTLS_PRINTF}" '%s\n' "${record}"
				} > "${EASYTLS_TEMP_UPDATE}"
			then
				# Success
				universal_update_ok=1
			else
				error_msg "universal_update - Add record"
			fi
		fi
	;;
	del)
		if "${EASYTLS_GREP}" -q "${record}" "${EASYTLS_TEMP_LIST}"; then
			# Delete record
			if "${EASYTLS_SED}" -e "/${record}/d" \
				"${EASYTLS_TEMP_LIST}" > "${EASYTLS_TEMP_UPDATE}"
			then
				# Success
				universal_update_ok=1
			else
				error_msg "universal_update - Delete record"
			fi
		else
			error_msg "universal_update - del - record does not exist"
		fi
	;;
	*)	die "universal_update - unknown action - ${action}"
	esac

	if [ -n "${universal_update_ok}" ]; then
		# Move temp file over target
		"${EASYTLS_RM}" -f "${target}"
		"${EASYTLS_MV}" "${EASYTLS_TEMP_UPDATE}" "${target}" || \
			die "universal_update - Move temp file over target"
	else
		return 1
	fi
} # => universal_update ()

# Generate file hash - Return $generated_valid_hash
generate_and_validate_file_hash () {
	[ -f "${1}" ] || {
		error_msg "generate_and_validate_file_hash - input file"
		unset -v target openssl_hash generated_hash
		return 1
		}

	target="${1}"

	# hash the file
	if [ -z "${force_hash}" ] && [ -n "${FILE_HASH_DISABLED}" ]; then
		# If NOT forced hash AND file hashing IS disabled = Fixed Hash
		generated_hash="${fixed_hash}"
	else
		if [ -z "${EASYTLS_HASH_ALGO}" ]; then
			error_msg "generate_and_validate_file_hash - EASYTLS_HASH_ALGO"
			unset -v target generated_hash
			return 1
		fi

		# Generate hash
		if easytls_ssl_generate_file_hash "${target}"; then
			openssl_hash="${generated_file_hash}"
			generated_hash="${openssl_hash%% *}"
			unset -v openssl_hash
		else
			error_msg "generate_and_validate_file_hash -"
			error_msg "- easytls_ssl_generate_file_hash"
			unset -v target openssl_hash generated_hash
			return 1
		fi

		# Verify generated hash
		if validate_hash "${generated_hash}"; then
			# Return generated_valid_hash
			generated_valid_hash="${generated_hash}"
		else
			error_msg "validate_hash - generate_and_validate_file_hash"
			unset -v target generated_hash
			return 1
		fi
	fi

	# Clean up
	unset -v target generated_hash
} # => generate_and_validate_file_hash ()

# Generate data hash
generate_and_validate_data_hash () {
	[ "${#}" -eq 1 ] || {
		help_note="input: ${*}"
		die "generate_and_validate_data_hash - invalid input"
		}

	# MUST not be a file-name
	[ ! -f "${1}" ] || \
		die "generate_and_validate_data_hash - MUST not be a file-name"

	# hash the data
	if [ -z "${force_hash}" ] && [ -n "${FILE_HASH_DISABLED}" ]; then
		# If NOT forced hash AND file hashing IS disabled = Fixed Hash
		generated_hash="${fixed_hash}"
	else
		[ -n "${EASYTLS_HASH_ALGO}" ] || {
			error_msg "generate_and_validate_data_hash - EASYTLS_HASH_ALGO"
			return 1
			}

		# Generate hash
		if easytls_ssl_generate_data_hash "${1}"; then
			generated_hash="${generated_data_hash}"
			unset -v generated_data_hash
		else
			error_msg "generate_and_validate_data_hash -"
			error_msg "- easytls_ssl_generate_data_hash"
			unset -v generated_data_hash
			return 1
		fi

		# Verify generated_hash
		if validate_hash "${generated_hash}"; then
			# Return validated generated_hash
			generated_valid_hash="${generated_hash}"
		else
			error_msg "generate_and_validate_data_hash - validate_hash"
			unset -v generated_hash
			return 1
		fi
	fi

	# Clean up
	unset -v generated_hash
} # => generate_and_validate_data_hash ()

# Verify valid hash - Only return success or fail
validate_hash () {
	[ "${#}" -eq 1 ] || {
		error_msg "validate_hash - invalid input: ${*}"
		return 1
		}

	validate_hash_block="$(( validate_hash_block + 1 ))"
	if [ -z "${auto_check}" ]; then
		# less than three is simplest
		[ "${validate_hash_block}" -lt 3 ] || {
			error_msg "validate_hash must only run twice"
			return 1
			}
	fi

	# fixed_hash is valid
	[ "${1}" != "${fixed_hash}" ] || return 0

	# If master-hash has been verified then
	# All further hashes are considered to be valid
	# This is already wildly abusive, so don't abuse it any further..
	if [ -z "${force_hash}" ] && [ -n "${disable_validate_hash}" ]; then
		return 0
	fi

	# Verify length
	[ "${#1}" -eq "${fixed_length}" ] || {
		error_msg "validate_hash - Verify length: ${#1}"
		unset -v test_hash #test_length
		return 1
		}

	# Verify hash is hex only
	# Any error will contain non-hex chars (hopefully)
	[ "${1}" = "${1%[!0123456789abcdefABCDEF]*}" ] || {
		error_msg "validate_hash - !hex"
		unset -v test_hash #test_length
		return 1
		}

	# Test for empty input hash
	[ "${1}" != "${forbidden_hash}" ] || {
		error_msg "validate_hash - forbidden_hash"
		unset -v test_hash #test_length
		return 1
		}
} # => validate_hash ()

# Verify two hashes Match
match_two_hashes () {
	[ "${#}" -eq 2 ] || return 1
	# Always validate prior to this test
	[ "${1}" = "${2}" ] && return 0
	# Check if either hash is fixed hash then ignore
	# Allows to switch between file-hash and file-hash-disabled mode
	# Using file-hash-disabled mode will taint the TLS PKI
	[ "${1}" = "${fixed_hash}" ] && return 0
	[ "${2}" = "${fixed_hash}" ] && return 0
	# Error is implicit
} # => match_two_hashes ()

# Save file hash
save_file_hash () {
	[ "${#}" -eq 2 ] || {
		help_note="input: ${*}"
		die "invalid input - save_file_hash"
		}

	hash_file="${1}" # File to save hash to
	valid_hash="${2}" # hash to save

	# Must be a valid target fle
	if [ "${hash_file}" = "${EASYTLS_FASTER_HASH}" ]; then
		# Save hash to target
		"${EASYTLS_CP}" "${hash_file}" "${hash_file}.tmp" || {
			error_msg "back up - save_file_hash"
			return 1
			}
		"${EASYTLS_PRINTF}" '%s' "${valid_hash}" > "${hash_file}" || {
			error_msg "save hash - save_file_hash"
			return 1
			}

	elif [ -f "${hash_file}" ]; then # An old target
		: # OK
	else
		error_msg "invalid target - save_file_hash"
		return 1
	fi

	# cleanup
	"${EASYTLS_RM}" -f "${hash_file}.tmp"
	unset -v hash_file valid_hash
} # => save_file_hash ()

# Read hash from file (without cat) and clear EOF error
read_hash_file () {
	[ -f "${1}" ] || return 1
	read -r <"${1}" saved_file_hash || :
} # => read_hash_file ()

# generate_and_match_valid_hash
generate_and_match_valid_hash () {
	[ "${#}" -eq 2 ] || {
		help_note="input: ${*}"
		die "invalid input - generate_and_match_valid_hash"
		}

	target_file="${1}" # File to be hashed
	hash_file="${2}" # File to read the hash from

	# Input error
	[ "${target_file}" != "${hash_file}" ] || {
		error_msg "generate_and_match_valid_hash - invalid input"
		unset -v target_file hash_file generated_valid_hash saved_file_hash
		return 1
		}

	# Generate $generated_valid_hash
	generate_and_validate_file_hash "${target_file}" || {
		error_msg "generate_and_match_valid_hash - generate_and_validate_file_hash"
		unset -v target_file hash_file generated_valid_hash saved_file_hash
		return 1
		}

	# Read hash from file
	read_hash_file "${hash_file}" || {
		error_msg "generate_and_match_valid_hash - read_hash_file"
		unset -v target_file hash_file generated_valid_hash saved_file_hash
		return 1
		}

	# Validate and match $generated_valid_hash
	match_two_hashes "${generated_valid_hash}" "${saved_file_hash}" || {
		error_msg "generate_and_match_valid_hash - match_two_hashes"
		unset -v target_file hash_file generated_valid_hash saved_file_hash
		return 1
		}

	unset -v target_file hash_file generated_valid_hash saved_file_hash
} # => generate_and_match_valid_hash ()

# generate and save file hash
generate_and_save_file_hash () {
	[ "${#}" -eq 2 ] || {
		unset -v help_note
		die "invalid input - generate_and_save_file_hash"
		}

	target_file="${1}" # File to hash
	hash_file="${2}" # File to save the hash to

	[ "${target_file}" != "${hash_file}" ] || {
		error_msg "generate_and_save_file_hash - invalid input"
		unset -v target_file hash_file generated_valid_hash
		return 1
		}

	# Generate $generated_valid_hash
	generate_and_validate_file_hash "${target_file}" || {
		error_msg "generate_and_save_file_hash - generate_and_validate_file_hash"
		unset -v target_file hash_file generated_valid_hash
		return 1
		}

	# Save $generated_valid_hash
	save_file_hash "${hash_file}" "${generated_valid_hash}" || {
		error_msg "generate_and_save_file_hash - save_file_hash"
		unset -v target_file hash_file generated_valid_hash
		return 1
		}

	unset -v target_file hash_file generated_valid_hash
} # => generate_and_save_file_hash ()



############################################################################
#
# EASYTLS openssl Section
#

# Generate openssl.cnf file and export OPENSSL_CONF for self-signed certs
easytls_ssl_file ()
{
	# SSL config file
	case "${1}" in
	destroy)
		unset -v OPENSSL_CONF
		[ -z "${EASYTLS_SSL_CONF}" ] || \
			"${EASYTLS_RM}" -f "${EASYTLS_SSL_CONF}"
	;;
	create)
		print "${openssl_full_version}"
		EASYTLS_SSL_CONF="${EASYTLS_DATA_DIR}/easytls-openssl.cnf"
		OPENSSL_CONF="${EASYTLS_SSL_CONF}"
		export OPENSSL_CONF
		"${EASYTLS_PRINTF}" '%s\n' \
'# For use with Easy-TLS and OpenSSL or LibreSSL

HOME					= .

####################################################################
[ ca ]
default_ca				= CA_default	# The default ca section

####################################################################
[ CA_default ]
# Comment out the following two lines for the traditional
# (and highly broken) format
name_opt 				= ca_default	# Subject Name options
cert_opt 				= ca_default	# Certificate field options

[ policy_anything ]
commonName		= supplied

####################################################################
[ req ]
default_bits			= 2048
distinguished_name		= req_distinguished_name
attributes				= req_attributes

# The extensions to add to the self signed cert
x509_extensions			= easytls

[ req_distinguished_name ]
commonName				= Common Name
commonName_max			= 64

[ req_attributes ]
challengePassword		= A challenge password
challengePassword_min	= 4
challengePassword_max	= 20

####################################################################
[ easytls ]
# Easy-TLS self-signed certificate
keyUsage 				= digitalSignature, keyEncipherment
basicConstraints 		= CA:true

# PKIX recommendation
subjectKeyIdentifier	= hash
authorityKeyIdentifier	= keyid:always,issuer
' > "${EASYTLS_SSL_CONF}" || return 1
	;;
	*)	die "easytls_ssl_file unknown option: $1"
	esac
} # => easytls_ssl_file ()

# SSL hash of empty data for forbidden hash
# shellcheck disable=SC2031 # modified in a subshell
ssl_generate_empty_hash ()
{
	[ -n "${unlock_ssl}" ] || return 1
	"${EASYTLS_PRINTF}" '%s' "" | \
		"${EASYRSA_OPENSSL}" dgst -"${EASYTLS_HASH_ALGO}" -r || return 1
} # => ssl_generate_empty_hash ()

# Extract the forbidden empty_hash from SSL hash
# shellcheck disable=SC2030 # subshell modification
easytls_ssl_generate_empty_hash ()
{
	error_out="easytls_ssl_generate_empty_hash - ssl_out"
	ssl_out="$(
		[ -z "$unlock_ssl" ] && unlock_ssl=1 || return 1
		ssl_generate_empty_hash
		)" || { error_msg "${error_out}"; return 1; }
	empty_hash="${ssl_out% *}"
	unset -v error_out ssl_out
} # => easytls_ssl_generate_empty_hash ()

# Hash all files from master file-list
ssl_generate_new_master_files_hash ()
{
	# DISABLE_DELETE
	die "ssl_generate_new_master_files_hash"
	#[ -n "${master_hash_only}" ] || return 1
	#"${EASYRSA_OPENSSL}" dgst -"${EASYTLS_HASH_ALGO}" -r "$@" | \
	#	"${EASYTLS_SED}" -e 's` .*``' || return 1
} # => openssl_generate_data_hash ()

# SSL data in via pipe hash output
ssl_generate_old_master_data_hash ()
{
	[ -n "${master_hash_only}" ] || return 1
	"${EASYRSA_OPENSSL}" dgst -"${EASYTLS_HASH_ALGO}" -r || return 1
} # => openssl_generate_data_hash ()

# SSL file via command hash output
# shellcheck disable=SC2031 # modified in a subshell
ssl_generate_file_hash ()
{
	[ -n "${unlock_ssl}" ] || return 1
	"${EASYRSA_OPENSSL}" dgst -"${EASYTLS_HASH_ALGO}" -r "${1}" || return 1
} # => ssl_generate_file_hash ()

# easytls wrapper for ssl file hash
# shellcheck disable=SC2030 # subshell modification
easytls_ssl_generate_file_hash ()
{
	# input MUST be an existing file-name
	[ -f "${1}" ] || missing_file "easytls_ssl_generate_file_hash - $1"
	if [ -n "${request_fixed_hash}" ]; then
		generated_file_hash="${fixed_hash}"
		return 0
	else
		error_out="easytls_ssl_generate_file_hash - ssl_out"
		ssl_out="$(
			[ -z "$unlock_ssl" ] && unlock_ssl=1 || return 1
			ssl_generate_file_hash "${1}"
			)" || { error_msg "${error_out}"; return 1; }
		generated_file_hash="${ssl_out% *}"
		unset -v error_out ssl_out
	fi
} # => easytls_ssl_encode_base64_data ()

# SSL data in via pipe hash output
# shellcheck disable=SC2031 # modified in a subshell
ssl_generate_data_hash ()
{
	[ -n "${unlock_ssl}" ] || return 1
	"${EASYRSA_OPENSSL}" dgst -"${EASYTLS_HASH_ALGO}" -r || return 1
} # => ssl_generate_data_hash ()

# easytls wrapper for ssl data hash
# shellcheck disable=SC2030 # subshell modification
easytls_ssl_generate_data_hash ()
{
	# input MUST not be a file-name
	[ ! -f "${1}" ] || \
		die "easytls_ssl_generate_data_hash - MUST not be a file $1"
	if [ -n "${request_fixed_hash}" ]; then
		generated_data_hash="${fixed_hash}"
		return 0
	else
		error_out="easytls_ssl_generate_data_hash - ssl_out"
		ssl_out="$(
			[ -z "$unlock_ssl" ] && unlock_ssl=1 || return 1
			"${EASYTLS_PRINTF}" '%s' "$1" | ssl_generate_data_hash
			)" || { error_msg "${error_out}"; return 1; }
		generated_data_hash="${ssl_out% *}"
		unset -v error_out ssl_out
	fi
} # => easytls_ssl_generate_data_hash ()

# SSL Base64 encode output
# shellcheck disable=SC2031 # modified in a subshell
ssl_encode_base64_data ()
{
	[ -n "${unlock_ssl}" ] || return 1
	"${EASYRSA_OPENSSL}" enc -e -a -A || return 1
} # => ssl_encode_base64_data ()

# easytls wrapper for ssl Base64 encode data
# shellcheck disable=SC2030 # subshell modification
easytls_ssl_encode_base64_data ()
{
	error_out="easytls_ssl_encode_base64_data - ssl_out"
	ssl_out="$(
		[ -z "$unlock_ssl" ] && unlock_ssl=1 || return 1
		"${EASYTLS_PRINTF}" '%s' "$1" | ssl_encode_base64_data
		)" || { error_msg "${error_out}"; return 1; }
	generated_base64="${ssl_out}"
	unset -v error_out ssl_out
} # => easytls_ssl_encode_base64_data ()

# SSL -enddate output
# shellcheck disable=SC2031 # modified in a subshell
ssl_cert_expire_date ()
{
	[ -n "${unlock_ssl}" ] || return 1
	"${EASYRSA_OPENSSL}" x509 -in "${1}" -noout -enddate || return 1
} # => openssl_cert_expire_date ()

# Extract expire date from SSL -enddate
# shellcheck disable=SC2030 # subshell modification
easytls_ssl_cert_expire_date ()
{
	error_out="easytls_ssl_cert_expire_date - ssl_out"
	ssl_out="$(
			[ -z "$unlock_ssl" ] && unlock_ssl=1 || return 1
		ssl_cert_expire_date "${1}"
		)" || { error_msg "${error_out}"; return 1; }
	certificate_expire_date="${ssl_out#*=}"
	unset -v error_out ssl_out
} # => easytls_ssl_generate_fingerprint ()

# SSL -purpose output (List of purposes Yes/No)
# shellcheck disable=SC2031 # modified in a subshell
ssl_cert_purpose ()
{
	[ -n "${unlock_ssl}" ] || return 1
	"${EASYRSA_OPENSSL}" x509 -in "${1}" -noout -purpose || return 1
} # => openssl_cert_purpose ()

# Return the purpose-list from SSL -purpose
# shellcheck disable=SC2030 # subshell modification
easytls_ssl_cert_purpose ()
{
	error_out="easytls_ssl_cert_purpose - ssl_out"
	ssl_out="$(
			[ -z "$unlock_ssl" ] && unlock_ssl=1 || return 1
		ssl_cert_purpose "${1}"
		)" || { error_msg "${error_out}"; return 1; }
	certificate_purpose_list="${ssl_out}"
	unset -v error_out ssl_out
} # => easytls_ssl_generate_fingerprint ()

# SSL -fingerprint output
# shellcheck disable=SC2031 # modified in a subshell
ssl_generate_fingerprint ()
{
	[ -n "${unlock_ssl}" ] || return 1
	"${EASYRSA_OPENSSL}" x509 -in "${1}" -noout -sha256 -fingerprint || return 1
} # => ssl_generate_fingerprint ()

# Extract the fingerprint from SSL -fingerprint
# shellcheck disable=SC2030 # subshell modification
easytls_ssl_generate_fingerprint ()
{
	error_out="easytls_ssl_generate_fingerprint - ssl_out"
	ssl_out="$(
			[ -z "$unlock_ssl" ] && unlock_ssl=1 || return 1
		ssl_generate_fingerprint "${1}"
		)" || { error_msg "${error_out}"; return 1; }
	certificate_fingerPrint="${ssl_out#*=}"
	unset -v error_out ssl_out
} # => easytls_ssl_generate_fingerprint ()

# SSL -serial output
# shellcheck disable=SC2031 # modified in a subshell
ssl_cert_serial ()
{
	[ -n "${unlock_ssl}" ] || return 1
	"${EASYRSA_OPENSSL}" x509 -in "${1}" -noout -serial || return 1
} # => ssl_cert_serial ()

# Extract the serialNumber from OpenSSL -serial
# shellcheck disable=SC2030 # subshell modification
easytls_ssl_cert_serial ()
{
	error_out="easytls_ssl_cert_serial - ssl_out"
	ssl_out="$(
			[ -z "$unlock_ssl" ] && unlock_ssl=1 || return 1
		ssl_cert_serial "${1}"
		)" || { error_msg "${error_out}"; return 1; }
	certificate_serialNumber="${ssl_out#*=}"
	unset -v error_out ssl_out
} # => easytls_ssl_cert_serial ()

# SSL -subject output and grep for commonName
# shellcheck disable=SC2031 # modified in a subshell
ssl_crt_common_name ()
{
	[ -n "${unlock_ssl}" ] || return 1
	"${EASYRSA_OPENSSL}" x509 -in "${1}" -noout -subject -nameopt utf8 \
		-nameopt sep_multiline -nameopt space_eq -nameopt lname | \
			"${EASYTLS_GREP}" '^[[:blank:]]*commonName' || return 1
} # => ssl_crt_common_name ()

# Extract the CommonName from OpenSSL -subject
# shellcheck disable=SC2030 # subshell modification
easytls_ssl_crt_common_name ()
{
	error_out="easytls_ssl_crt_common_name - ssl_out"
	ssl_out="$(
			[ -z "$unlock_ssl" ] && unlock_ssl=1 || return 1
		ssl_crt_common_name "${1}"
		)" || { error_msg "${error_out}"; return 1; }
	certificate_commonName="${ssl_out#*commonName = }"
	unset -v error_out ssl_out
} # => easytls_ssl_crt_common_name ()

# Verify certificate purpose
verify_cert_purpose ()
{
	[ -f "${1}" ] || {
		error_msg "verify_cert_purpose - input file"
		return 1
		}

	[ -n "${2}" ] || {
		error_msg "verify_cert_purpose - purpose requested p_r"
		return 1
		}
	# purpose requested
	p_r="${2}"

	# Get certificate purpose list
	easytls_ssl_cert_purpose "${1}" || {
		error_msg "verify_cert_purpose - easytls_ssl_cert_purpose"
		return 1
		}
	# Use purpose list
	p_l="${certificate_purpose_list}"
	unset -v certificate_purpose_list

	# Search for requested purpose
	while IFS="$new_line" read -r i
	do
		if [ "${i}" = "${p_r}" ]; then
			return 0
		fi
	done << PURPOSE_LIST
${p_l}
PURPOSE_LIST

	return 1
} # => verify_cert_purpose ()

# Cut to only certificate enddate
cert_expire_date ()
{
	# DISABLED_DELETE
	#die "cert_expire_date"
	[ -f "${1}" ] || missing_file "${1}"
	easytls_ssl_cert_expire_date "${1}" || {
		error_msg "cert_expire_date - easytls_ssl_cert_expire_date"
		return 1
		}
	cert_expire="${certificate_expire_date}"
	unset -v certificate_expire_date
} # => crt_expire_date ()

# Get x509 certificate expiry Date
cert_expire ()
{
	easytls_verbose
	name="${1}"

	if [ -n "${EASYTLS_NO_CA}" ]; then

	# Begin NO CA MODE
	if [ -n "${name}" ]; then
		cert_file="${EASYTLS_PKI}/${name}.crt"
		cert_expire_date "${cert_file}" || return 1
		print " Certificate: ${cert_file}   Expire-date: ${cert_expire}"
	else
		for cert_file in "${EASYTLS_PKI}"/*.crt
		do
			cert_expire_date "${cert_file}" || return 1
		print " Certificate: ${cert_file}   Expire-date: ${cert_expire}"
		done
	fi
	# End NO CA MODE

	else

	# Begin CA MODE
	verify_ca_init
	if [ "${name}" = "ca" ]; then
		cert_file="${EASYRSA_PKI}/ca.crt"
		cert_expire_date "${cert_file}" || return 1
		print " Certificate: ca.crt   Expire-date: ${cert_expire}"

	elif [ -n "${name}" ]; then
		cert_file="${EASYRSA_PKI}/issued/${name}.crt"
		cert_expire_date "${cert_file}" || return 1
		print " Certificate: ${name}.crt   Expire-date: ${cert_expire}"

	else
		# subshell
		easyrsa_valid_list="$(
			"${EASYTLS_GREP}" '^V' "${EASYRSA_INDEX}"|status_cn
			)" || {
				error_msg "cert_expire - easyrsa_valid_list"
				return 1
				}

		for name in ${easyrsa_valid_list}; do
			cert_file="${EASYRSA_PKI}/issued/${name}.crt"
			cert_expire_date "${cert_file}" || return 1
		print " Certificate: ${name}.crt   Expire-date: ${cert_expire}"
		done
		cert_file="${EASYRSA_PKI}/ca.crt"
		cert_expire_date "${cert_file}" || return 1
		print " Certificate: ca.crt   Expire-date: ${cert_expire}"
	fi
	# End CA MODE
	fi
	easytls_verbose
} # => cert_expire ()



############################################################################
#
# EASYTLS STATUS Section
#

# Show Easy-TLS inlines and Easy-RSA certificates
# Report invalid inline files due to certificate revokation
# Report duplicate CNs in EasyRSA due to certificate renewal
status ()
{
	[ -n "${auto_check}" ] || status_disabled_auto_check=1
	subsection="$1"

	# Clear lists
	ersa_valid_cn_list=""
	ersa_valid_serial_list=""
	ersa_revoked_cn_list=""
	ersa_revoked_serial_list=""
	#easytls_valid_cn_list=""
	etls_valid_name_list=""
	etls_valid_serial_list=""
	#easytls_revoked_serial_list="" # Not used
	etls_valid_tlskey_list=""

	[ -n "${auto_check}" ] || easytls_verbose
	# Invoke sub-section if specified
	if [ -n "${subsection}" ]; then
		case "${subsection}" in
		val|valid)
			status_easyrsa_valid || {
				error_msg "status - status_easyrsa_valid"
				return 1
				}
		;;
		rev|revoked)
			status_easyrsa_revoked || {
				error_msg "status - status_easyrsa_revoked"
				return 1
				}
		;;
		inl|inline)
			status_easytls_inline || {
				error_msg "status - status_easytls_inline"
				return 1
				}
		;;
		inv|invalid)
			silent_status=1
			status_easyrsa_valid || {
				error_msg "status - invalid - status_easyrsa_valid"
				return 1
				}
			status_easyrsa_revoked || {
				error_msg "status - invalid - status_easyrsa_revoked"
				return 1
				}
			status_easytls_inline || {
				error_msg "status - invalid - status_easytls_inline"
				return 1
				}
			unset -v silent_status
			status_easytls_invalid || {
				error_msg "status - invalid - status_easytls_invalid"
				return 1
				}
		;;
		ren|renewed)
		if [ -z "${auto_check}" ]; then
			silent_status=1
			status_easyrsa_valid || {
				error_msg "status - renewed - status_easyrsa_valid"
				return 1
				}
			status_easyrsa_revoked || {
				error_msg "status - renewed - status_easyrsa_revoked"
				return 1
				}
			status_easytls_inline || {
				error_msg "status - renewed - status_easytls_inline"
				return 1
				}
		fi
			unset -v silent_status
			status_easyrsa_renewed || {
				error_msg "status - renewed - status_easyrsa_renewed"
				return 1
				}
		;;
		dis|disabled)
			status_disabled_list || {
				error_msg "status - status_disabled_list"
				return 1
				}
		;;
		tls|tlskeys)
			status_easytls_tlskeys || {
				error_msg "status - status_easytls_tlskeys"
				return 1
				}
		;;
		*)
			error_msg "Unknown status option"
			return 1
		esac
		return 0
	fi

	print "Easy-RSA: Valid X509 certificates:"
	status_easyrsa_valid
	[ -n "${ersa_valid_serial_list}" ] || \
		print "  No Valid X509 certificates found"

	print "Easy-RSA: Revoked X509 certificates:"
	status_easyrsa_revoked
	[ -n "${ersa_revoked_serial_list}" ] || \
		print "  No Revoked X509 certificates found"

	print "Easy-TLS: Known inline files:"
	status_easytls_inline
	[ -n "${etls_valid_serial_list}" ] || \
		print "  No inline files found"

	print "Easy-TLS: Invalid inline files:"
	revoked_mismatch_count=0
	status_easytls_invalid
	if [ "${revoked_mismatch_count}" -gt 0 ]; then
		print "  Invalid inline files are caused by revoked X509 Certificates"
		print "  To solve this use: ./easytls remove-inline <filename-base>"
	else
		print "  No invalid inline files found"
	fi

	print "Easy-TLS: .inline serial number mismatches:"
	renewed_mismatch_count=0
	status_easyrsa_renewed
	if [ "${renewed_mismatch_count}" -gt 0 ]; then
		print "  Mismatches are caused by X509 Certificates"
		print "  which have not been completely renewed by EasyRSA"
		print "  To solve this use: caution.."
	else
		print "  No mismatches found"
	fi

	print "Easy-TLS: Known TLS keys:"
	status_easytls_tlskeys
	[ -n "${etls_valid_tlskey_list}" ] || \
		print "  No TLS keys found"

	easytls_verbose "Status complete"
	[ -n "${auto_check}" ] || easytls_verbose
} #=> status ()

# Format status output from Easy-RSA
status_easyrsa_output ()
{
	"${EASYTLS_AWK}" '{
		if (NF == 4)
		print "  Common-Name " $1 "\t  X509-Serial " $2 "\t  Sub-key " $4
		else
		print "  Common-Name " $1 "\t  X509-Serial " $2
		}'
} # => status_easyrsa_output ()

# Format status output from Easy-TLS
status_easytls_output ()
{
	"${EASYTLS_AWK}" '{
		if ( $4 == "NOSUBKEY" )
		print "  Common-Name " $3 "\t  X509-Serial " $2
		else
		print "  Common-Name " $3 "\t  X509-Serial " $2 "\t  Sub-key " $4
		}'
} # => status_easytls_output ()

# Format status output from Easy-TLS
noca_status_easytls_output ()
{
	"${EASYTLS_AWK}" '{
		if ( NF == 2 )
		print "  Common-Name " $1 "\t  X509-Serial " $2
		else
		print "  Common-Name " $1 "\t  X509-Serial " $2 "\t  Sub-key " $3
		}'
} # => noca_status_easytls_output ()

# Format status CN
status_cn ()
{
	"${EASYTLS_SED}" -e 's`^.*/CN=``g' -e 's`/.*$``g'
} # => status_cn ()

# Extract Valid records from OpenSSL index.txt by crt_serial
status_valid_extract_cn ()
{
	"${EASYTLS_GREP}" "^V.*[[:blank:]]${i}[[:blank:]]" "${EASYRSA_INDEX}" | \
		status_cn
} # => status_valid_extract_cn ()

# Extract Revoked records from OpenSSL index.txt by crt_serial
status_revoked_extract_cn ()
{
	"${EASYTLS_GREP}" "^R.*[[:blank:]]${i}[[:blank:]]" "${EASYRSA_INDEX}" | \
		status_cn
} # => status_revoked_extract_cn ()

# Search ersa revoked serial list for etls valid serial
status_search_revoked_serial_list()
{
	"${EASYTLS_PRINTF}" '%s\n' "${ersa_revoked_serial_list}" | \
		"${EASYTLS_GREP}" -c "^${inline_serial}$"
} # => status_search_revoked_serial_list()

# List the invalid inline file record by serial number
# Do not terminate regex with $ because we need to allow for subs
status_invalid_inline_list ()
{
	"${EASYTLS_GREP}" \
		"^${verified_inline_hash}[[:blank:]]${inline_serial}[[:blank:]]" \
		"${EASYTLS_INLINE_INDEX}" | status_easytls_output
} # => status_invalid_inline_list ()

# Print Valid certs from Easy-RSA
status_easyrsa_valid ()
{
	[ -f "${EASYRSA_INDEX}" ] || return 0
	ersa_valid_serial_list="$("${EASYTLS_GREP}" '^V' "${EASYRSA_INDEX}" | \
		"${EASYTLS_AWK}" '{print $3}')"

	for i in ${ersa_valid_serial_list}; do
		ersa_valid_cn="$(status_valid_extract_cn)"
		ersa_valid_cn_list="${ersa_valid_cn_list} ${ersa_valid_cn}"
		[ -n "${silent_status}" ] || \
			print "${ersa_valid_cn} ${i}" | status_easyrsa_output
	done
} # => status_easyrsa_valid ()

# Simple status for No CA mode
noca_status ()
{
	print "Easy-RSA: Valid X509 certificates:"
	noca_status_x509_valid || {
		error_msg "noca_status_x509_valid - noca_status"
		return 1
		}
	print "Easy-TLS: Known inline files:"
	noca_status_easytls_inline || {
		error_msg "noca_status_easytls_inline - noca_status"
		return 1
		}
	print "Easy-TLS: Known TLS keys:"
	status_easytls_tlskeys || {
		error_msg "status_easytls_tlskeys - noca_status"
		return 1
		}
} # => noca_status ()

# Print Valid certs from Easy-RSA
noca_status_x509_valid ()
{
	for i in "${EASYTLS_PKI}"/*.crt; do

		[ -f "${i}" ] || continue

		easytls_ssl_crt_common_name "${i}" || {
			error_msg "noca_status_x509_valid - easytls_ssl_crt_common_name"
			return 1
			}
		ersa_valid_cn="${certificate_commonName}"
		unset -v certificate_commonName

		ersa_valid_cn="${ersa_valid_cn##*=CN = }"
		ersa_valid_cn_list="${ersa_valid_cn_list} ${ersa_valid_cn}"

		x509_cert_serial "${i}" || {
			error_msg "noca_status_x509_valid - x509_cert_serial"
			return 1
			}

		# Use extracted_cert_serial
		cert_serial="${extracted_cert_serial}"

		[ -n "${silent_status}" ] || \
			print "${ersa_valid_cn} ${cert_serial}" | status_easyrsa_output
	done
} # => noca_status_x509_valid ()

# Print known inline files from Easy-TLS
noca_status_easytls_inline ()
{
	for i in "${EASYTLS_PKI}"/*.inline; do

		[ -f "${i}" ] || continue

		etls_valid_name="$("${EASYTLS_GREP}" '^. Common name: ' "${i}")" || {
			error_msg "etls_valid_name - noca_status_easytls_inline"
			return 1
			}
		etls_valid_name="${etls_valid_name##*Common name: }"

		etls_valid_subkey="$("${EASYTLS_GREP}" '^. Sub-key name: ' "${i}")" || {
			etls_valid_subkey=""
			}
		etls_valid_subkey="${etls_valid_subkey##*Sub-key name: }"

		etls_valid_serial="$("${EASYTLS_GREP}" '^# X509 serial: ' "${i}")" || {
			error_msg "etls_valid_serial - noca_status_easytls_inline"
			return 1
			}
		etls_valid_serial="${etls_valid_serial##*X509 serial: }"

		[ -n "${silent_status}" ] || \
			print "${etls_valid_name} ${etls_valid_serial} ${etls_valid_subkey}" \
				| noca_status_easytls_output
		unset -v etls_valid_name etls_valid_subkey etls_valid_serial
	done
} # => noca_status_easytls_inline ()

# Print Revoked certs from Easy-RSA
status_easyrsa_revoked ()
{
	[ -f "${EASYRSA_INDEX}" ] || return 0
	ersa_revoked_serial_list="$("${EASYTLS_GREP}" '^R' "${EASYRSA_INDEX}" | \
		"${EASYTLS_AWK}" '{print $4}')"

	for i in ${ersa_revoked_serial_list}; do
		ersa_revoked_cn="$(status_revoked_extract_cn)"
		ersa_revoked_cn_list="${ersa_revoked_cn_list} ${ersa_revoked_cn}"
		[ -n "${silent_status}" ] || print "${ersa_revoked_cn} ${i}" | \
			status_easyrsa_output
	done
} # => status_easyrsa_revoked ()

# Print known inline files from Easy-TLS
status_easytls_inline ()
{
	[ -f "${EASYTLS_INLINE_INDEX}" ] || return 0
	etls_valid_name_list="$(inline_index_cn_subname_list)"
	etls_valid_serial_list="$(inline_index_serial_number_list)"
	etls_valid_il_hash_list="$(inline_index_ilhash_number_list)"
	[ -n "${silent_status}" ] || \
		"${EASYTLS_SED}" /^#/d "${EASYTLS_INLINE_INDEX}" | status_easytls_output
} # => status_easytls_inline ()

# Check Easy-TLS valid vs Easy-RSA revoked
status_easytls_invalid ()
{
	revoked_mismatch_count=0
	for i in ${etls_valid_il_hash_list}; do
		known_inline_hash="${i}"
		[ -n "${known_inline_hash}" ] || die "status invalid: known_inline_hash ?"

		# This needs to handle duplicate-ish names.
		name="$(inline_index_ilhash_2_cn)"
		sub_name="$(inline_index_ilhash_2_subname)"

		if [ "${sub_name}" = 'NOSUBKEY' ]; then
			# No sub name present
			inline_file="${EASYTLS_PKI}/${name}.inline"
		else
			# Append '-$sub_name' to inline file name
			inline_file="${EASYTLS_PKI}/${name}-${sub_name}.inline"
		fi
		[ -f "${inline_file}" ] || missing_file "${inline_file}"

		inline_serial="$(inline_index_ilhash_to_serial)"

		# Clear inline_hash from previous loop
		unset -v inline_hash
		# Check the .inline HASH
		if [ -z "${auto_check}" ]; then
			inline_file_verify_hash || die "Failed HASH: ${inline_file}"
			# Variable: verified_inline_hash, now exists
			mismatches="$(status_search_revoked_serial_list)"
		else
			mismatches=0
		fi

		case "${mismatches}" in
		0)	: ;; # No mismatches found
		1)
			revoked_mismatch_count=$((revoked_mismatch_count+1))
			[ -n "${auto_check}" ] || status_invalid_inline_list
		;;
		*)
			warn "EasyRSA duplicate records detected!"
			[ $((mismatches)) -gt 1 ] || \
				die "Error in mismatches: ${mismatches}"
		esac
	done
} # => status_easytls_invalid ()

# Print Renewed certs from Easy-RSA if they are inlined
status_easyrsa_renewed ()
{
	renewed_mismatch_count=0
	# REMEMBER: If there is no inline file for a cert
	# then EasyTLS does not care if the cert is renewed

	for i in ${etls_valid_name_list}; do

	name="${i}"
	inline_file="${EASYTLS_PKI}/${name}.inline"
	[ -f "${inline_file}" ] || missing_file "${inline_file}"

	# Inline serial
	x509_cert_serial "${inline_file}" || {
		error_msg "status_easyrsa_renewed - x509_cert_serial - ${inline_file}"
		return 1
		}

	# Use extracted_cert_serial
	inline_serial="${extracted_cert_serial}"

	cert_file="${EASYRSA_PKI}/issued/${name}.crt"
	if [ -f "${cert_file}" ]; then

		# Certificate serial
		x509_cert_serial "${cert_file}" || \
			die "status_easyrsa_renewed - x509_cert_serial - ${cert_file}"

		# Use extracted_cert_serial
		cert_serial="${extracted_cert_serial}"

		if [ "${inline_serial}" = "${cert_serial}" ]; then
			: # serial match, inline is upto Date
		else
			# serial mismatch, cert has been renewed in EasyRSA
			renewed_mismatch_count=$((renewed_mismatch_count+1))
			if [ -n "${silent_status}" ]; then
				# No output
				:
			else
				[ -n "${auto_check}" ] || {
					print "  Common-Name ${name}"
					print "    Inline-Serial ${inline_serial}"
					print "    Cert-Serial   ${cert_serial}"
					}
			fi
		fi
	else
		: # EasyRSA renew is broken, cert is probably revoked
	fi
	done # End of for loop
} # => status_easyrsa_renewed ()

# List known TLS keys
status_easytls_tlskeys ()
{
	# This while is the entire function - indent is wrong for longlines
	while read -r tlskey_serial cert_serial common_name sub_key; do

	# Skip heading
	[ "${tlskey_serial}" = "#" ] && continue

	# Set name and sub-key-name
	full_name="${common_name}"
	[ "${sub_key}" = 'NOSUBKEY' ] || full_name="${common_name}-${sub_key}"

	# Validate - Can not open this key so these checks are minor
	key_file="${EASYTLS_PKI}/${full_name}-tls-crypt-v2.key"
	metadata_debug="${EASYTLS_META_DATA_D}/${full_name}-tls-crypt-v2.metadata"

	[ -f "${key_file}" ] || "${EASYTLS_PRINTF}" '%s\t'   "  Missing key"

	if [ "${tlskey_serial%%:*}" != "${tlskey_serial}" ]; then
		# Must be a server key with no metadata
		"${EASYTLS_PRINTF}" '%s\t' "  Server"
	else
		[ -f "${metadata_debug}" ] || \
			"${EASYTLS_PRINTF}" '%s\t' "  * Missing metadata"
		"${EASYTLS_PRINTF}" '%s\t' "    Client"
	fi

	"${EASYTLS_PRINTF}" '%s  ' "${tlskey_serial}"
	"${EASYTLS_PRINTF}" '%s  ' "Common-Name ${common_name}"
	[ "${sub_key}" = 'NOSUBKEY' ] || \
		"${EASYTLS_PRINTF}" '%s  ' "sub-key ${sub_key}"
	"${EASYTLS_PRINTF}" '\n'

	# List
	etls_valid_tlskey_list="${etls_valid_tlskey_list} ${tlskey_serial}"

	done < "${EASYTLS_TLSKEY_INDEX}"
} # => status_easytls_tlskeys ()

# List disabled client keys
status_disabled_list ()
{
	"${EASYTLS_CAT}" "${EASYTLS_DISABLED_LIST}"
	easytls_verbose
} # => status_disabled_list ()

# Record status changes
status_update ()
{
	if [ -n "${FILE_HASH_DISABLED}" ]; then
		flash_config=1
		easytls_config status 1
	fi
} # => status_update ()



############################################################################
#
# EASYTLS INLINE-INDEX Section
#

# Get inline certificate expiry Date
inline_expire ()
{
	easytls_verbose
	name="${1}"

	if [ -n "${name}" ]; then
		inline_file="${EASYTLS_PKI}/${name}.inline"
		[ -f "${inline_file}" ] || missing_file "${inline_file}"
		inline_expire_date
		print " Certificate: ${name}.inline   Expire-date: ${inline_expire}"
	else
	# Get a list of Valid names from easytls-inline-index
	etls_valid_name_list="$(inline_index_cn_subname_list)"
		for name in ${etls_valid_name_list}; do
			inline_file="${EASYTLS_PKI}/${name}.inline"
			[ -f "${inline_file}" ] || missing_file "${inline_file}"
			inline_expire_date
		print " Inline-file: ${name}.inline   Expire-date: ${inline_expire}"
		done
	fi
	easytls_verbose
} # => inline_expire ()

# Extract enddate from .inline certificate
inline_expire_date ()
{
	inline_expire="$("${EASYTLS_GREP}" '^[[:blank:]]*Not After : ' \
		"${inline_file}")"
	inline_expire="${inline_expire##*: }"
} # => inline_expire_date ()

# Get a list of serial_numbers from inline-index
inline_index_serial_number_list ()
{
	"${EASYTLS_AWK}" '{if ($1 ~ /[^#]/) print $2 " "}' "${EASYTLS_INLINE_INDEX}"
} # => inline_index_serial_number_list ()

# Get a list of inline hashes from inline-index
inline_index_ilhash_number_list ()
{
	"${EASYTLS_AWK}" '{if ($1 ~ /[^#]/) print $1 " "}' "${EASYTLS_INLINE_INDEX}"
} # => inline_index_ilhash_number_list ()

# Get a list of Common_name + Sub-key name from inline-index
inline_index_cn_subname_list ()
{
	"${EASYTLS_AWK}" '{if($1 ~ /[^#]/)
		 {if($4 == "NOSUBKEY") {print $3 " ";} else {print $3 "-" $4 " ";}}
		 }' "${EASYTLS_INLINE_INDEX}"
} # => inline_index_cn_subname_list ()

# Keep an inline-index file for inline certs to manage revoke/renew
inline_index_update ()
{
	il_action="${1}"

	if [ "${il_action}" = 'del' ] && [ -n "${force_remove}" ]; then
		il_action='force-del'
	fi

	case "${il_action}" in
	add)
		# Inline serial
		x509_cert_serial "${inline_file}" || {
			error_msg "inline_index_update - x509_cert_serial"
			return 1
			}

		# Use extracted_cert_serial
		inline_serial="${extracted_cert_serial}"

		# Create new inline file HASH
		force_hash=1
		generate_and_validate_file_hash "${inline_file}" || {
				error_msg "inline_index_update - generate_and_validate_file_hash"
				return 1
				}

		# Set new inline file HASH
		current_inline_hash="${generated_valid_hash}"
		unset -v force_hash generated_valid_hash

		# Create new record
		new_record="${current_inline_hash} ${inline_serial} ${name}"

		# Append tlskey name and serial (Clients only)
		if [ -n "${tlskey_serial}" ] && [ -n "${TLSKEY_SUBNAME}" ]; then
			new_record="${new_record} ${TLSKEY_SUBNAME} ${tlskey_serial}"
		else
			error_msg "inline_index_update - ${TLSKEY_SUBNAME} ${tlskey_serial}"
			return 1
		fi

		easytls_verbose "  ADD: ${new_record}"

		# universal_update
		if universal_update add "${EASYTLS_INLINE_INDEX}" "${new_record}"; then
			: # ok
			update_master_hash=1
			return 0
		else
			error_msg "inline_index_update - universal_update - add"
			return 1
		fi
	;;
	del)
		# Note: Inline HASH is unique, regardless of --sub-key-name
		# Identify old record
		old_record="${verified_inline_hash}[[:blank:]]${inline_serial}[[:blank:]]"

		easytls_verbose "  DEL: ${old_record}"

		if universal_update del "${EASYTLS_INLINE_INDEX}" "${old_record}"; then
			: # ok
			update_master_hash=1
			return 0
		else
			error_msg "inline_index_update - universal_update - del"
			return 1
		fi
	;;
	force-del)
		# Build old record without inline file HASH, with --sub-key-name
		# because my earlier comment was over confident ;-)
		old_record="[[:xdigit:]]*[[:blank:]]${inline_serial}"
		old_record="${old_record}[[:blank:]]${name}"
		old_record="${old_record}[[:blank:]]${TLSKEY_SUBNAME}"

		easytls_verbose "  DEL: ${old_record}"

		# Find old record
		"${EASYTLS_GREP}" -q "^${old_record}" "${EASYTLS_INLINE_INDEX}" || {
			error_msg "inline_index_update force-del: Find ${old_record}"
			return 1
			}

		# Remove old record
		"${EASYTLS_SED}" -i -e "/^${old_record}.*$/d" \
			"${EASYTLS_INLINE_INDEX}" || {
				error_msg "inline_index_update force-del: Remove old record"
				return 1
				}
	;;
	*)
		error_msg "Unknown index action: ${il_action}"
		return 1
	esac

	easytls_verbose "Inline Index Update complete!"
	update_master_hash=1
} # => inline_index_update ()

# Verify current inline-index hash
inline_index_verify_hash ()
{
	# DISABLED_DELETE
	die "DISABLED inline_index_verify_hash"
	[ -z "${inline_index_verify_hash_block}" ] || \
		die "inline index verify hash must only run once"
	request_fixed_hash=1
	generate_and_match_valid_hash \
		"${EASYTLS_INLINE_INDEX}" "${EASYTLS_INLINE_X_HASH}" || {
			error_msg "generate_and_match_valid_hash - inline_index_verify_hash"
			return 1
			}
	unset -v request_fixed_hash
	easytls_verbose "Inline-index hash check OK"
	inline_index_verify_hash_block=1
} # => inline_index_verify_hash ()

# Save new inline-index hash
inline_index_save_hash ()
{
	# DISABLED_DELETE
	die "DISABLED inline_index_save_hash"
	[ -z "${inline_index_save_hash_block}" ] || \
		die "inline index save hash must only run once"
	request_fixed_hash=1
	generate_and_save_file_hash \
		"${EASYTLS_INLINE_INDEX}" "${EASYTLS_INLINE_X_HASH}" || {
			error_msg "generate_and_save_file_hash - inline_index_save_hash"
			return 1
			}
	unset -v request_fixed_hash
	easytls_verbose "Inline-index hash save OK"
	update_master_hash=1
	inline_index_save_hash_block=1
} # => inline_index_save_hash ()

# Check inline-file hash
inline_file_verify_hash ()
{
	# This is a check so check everything here
	# every value and file must exist at this time
	# Try to logically illiminate the need for these checks

	[ -n "${name}" ] || \
		die "inline_file_verify_hash: Missing value: name"

	# May not require this
	#[ -n "$crt_serial" ] || \
	#	die "inline_file_verify_hash: Missing value: crt_serial"

	# Must have inline_serial
	[ -n "${inline_serial}" ] || \
		die "inline_file_verify_hash: Missing value: inline_serial"

	# Should not have this HASH, that is the reason to do this check
	# If we already have a HASH then something else is wrong
	[ -z "${inline_hash}" ] || \
		die "inline_file_verify_hash: Found value: inline_hash ${inline_hash}"

	# Already have inline_serial so this file MUST exist so remove test
	# Not so during an index rebuild
	[ -n "${inline_file}" ] || \
		die "inline_file_verify_hash: Missing value: inline_file"
	[ -f "${inline_file}" ] || missing_file "${inline_file}"

	# generate current file HASH
	if [ "${auto_check}" ]; then
		: # Ignore, improve auto-check
	else
		force_hash=1
		validate_hash_block="$(( validate_hash_block - 1 ))"
		generate_and_validate_file_hash "${inline_file}" || {
			die "inline_file_verify_hash - generate_and_validate_file_hash"
			}
	fi

	# Use the hash
	inline_file_hash="${generated_valid_hash}"
	unset -v force_hash generated_valid_hash

	# Search Inline Index for Inline File Hash
	# Do not need to search for --sub-key-name
	# because inline file HASH ${inline_file_hash} is unique
	if "${EASYTLS_GREP}" -q \
		"^${inline_file_hash}[[:blank:]]${inline_serial}[[:blank:]]" \
		"${EASYTLS_INLINE_INDEX}"
	then
		# hash OK
		verified_inline_hash="${inline_file_hash}"
		return 0
	fi

	# There is only one way out of this..
	error_msg "Inline file hash failed"
	return 1
} # => inline_file_verify_hash ()

# Get Common name from inline index file using inline hash as key
inline_index_ilhash_2_cn ()
{
	script="{if(\$1 ~ /^${known_inline_hash}\$/) print \$3}"
	"${EASYTLS_AWK}" "${script}" "${EASYTLS_INLINE_INDEX}"
	unset -v script
} # => inline_index_ilhash_2_cn ()

# Get sub name from inline index file using inline hash as key
inline_index_ilhash_2_subname ()
{
	script="{if(\$1 ~ /^${known_inline_hash}\$/) print \$4}"
	"${EASYTLS_AWK}" "${script}" "${EASYTLS_INLINE_INDEX}"
	unset -v script
} # => inline_index_ilhash_2_subname ()

# Get cert serial number from inline index file using inline hash as key
inline_index_ilhash_to_serial ()
{
	script="{if(\$1 ~ /^${known_inline_hash}\$/) print \$2}"
	"${EASYTLS_AWK}" "${script}" "${EASYTLS_INLINE_INDEX}"
	unset -v script
} # => inline_index_ilhash_to_serial ()

# Get common_name from inline file
inline_crt_common_name ()
{
	# DISABLED_DELETE
	die "inline_crt_common_name DISABLED"
	#icn="$("${EASYTLS_GREP}" '^# Common name: .*$' "${inline_file}")"
	#inline_common_name="${icn##*name: }"
	#unset -v icn
} # => inline_crt_common_name ()

# Get tlskey-serial from inline-file
inline_tlskey_serial ()
{
	script="{if(\$2 ~ /^UV_TLSKEY_SERIAL\$/) print \$3}"
	"${EASYTLS_AWK}" "${script}" "${inline_file}"
	unset -v script
} # => inline_tlskey_serial ()

# Format OpenSSL serial number output
x509_cert_serial ()
{
	[ -f "${1}" ] || {
		error_msg "x509_cert_serial - input file"
		return 1
		}

	easytls_ssl_cert_serial "${1}" || {
		error_msg "x509_cert_serial - extracted_cert_serial"
		return 1
		}
	extracted_cert_serial="${certificate_serialNumber}"
} # => x509_cert_serial ()

# Check inline hash and Copy inline to stdout
inline_show ()
{
	[ "$#" -ge 1 ] || die "Required option(s): <filename_base>"

	name="${1}"
	inline_file="${EASYTLS_PKI}/${name}.inline"

	[ "${TLSKEY_SUBNAME}" = 'NOSUBKEY' ] || {
		inline_file="${EASYTLS_PKI}/${name}-${TLSKEY_SUBNAME}.inline"
		}

	[ -f "${inline_file}" ] || missing_file "${inline_file}"

	# Inline serial
	x509_cert_serial "${inline_file}" || {
		error_msg "inline_show - x509_cert_serial"
		return 1
		}

	# Use extracted_cert_serial
	inline_serial="${extracted_cert_serial}"

	# Check inline hash
	inline_file_verify_hash || die "Failed HASH: ${inline_file}"

	# Copy inline to stdout
	easytls_verbose
	"${EASYTLS_CAT}" "${inline_file}" || die "Failed to open: ${inline_file}"
	easytls_verbose
} # => inline_show ()

# Remove .inline file
remove_inline ()
{
	[ "$#" -ge 1 ] || die "Required option(s): <filename_base>"

	name="${1}"
	inline_file="${EASYTLS_PKI}/${name}.inline"

	[ "${TLSKEY_SUBNAME}" = 'NOSUBKEY' ] || {
		inline_file="${EASYTLS_PKI}/${name}-${TLSKEY_SUBNAME}.inline"
		}

	[ -f "${inline_file}" ] || missing_file "${inline_file}"

	# Inline serial
	x509_cert_serial "${inline_file}" || {
		error_msg "remove_inline - x509_cert_serial"
		return 1
		}

	# Use extracted_cert_serial
	inline_serial="${extracted_cert_serial}"

	unset -v force_remove
	# Check .inline HASH prior to removal
	if inline_file_verify_hash; then
		: # OK
	else
		unset -v error_log
		print "
* HELP: easytls can *Force* remove this file from the index.
        Once removed from the index, easytls will ignore this file.
        The file will be renamed to: ${inline_file}-badhash"
		confirm  "*Force* remove inline file: ${inline_file} ? " "yes" \
			"WARNING: Inline file HASH failed !"
		force_remove=1
	fi

	# Confirm remove
	[ -n "${force_remove}" ] || \
		confirm  "Remove inline file ? " "yes" "Remove: ${inline_file}"

	# Do not over write existing
	[ ! -f "${inline_file}-badhash" ] || \
		die "File exists: ${inline_file}-badhash"

	# Move
	if [ -z "${force_remove}" ]; then
		"${EASYTLS_MV}" "${inline_file}" "${EASYTLS_TEMP_DELETED}" || \
			die "Failed to move: ${inline_file}"
	else
		"${EASYTLS_MV}" "${inline_file}" "${inline_file}-badhash" || \
			die "Failed to move: ${inline_file}"
	fi

	# Update the index first
	if inline_index_update del; then
		# Remove
		if [ -z "${force_remove}" ]; then
			"${EASYTLS_RM}" "${EASYTLS_TEMP_DELETED}" || \
				die "Failed to remove: ${inline_file}"
		fi
	else
		# Undo move
		if [ -z "${force_remove}" ]; then
			"${EASYTLS_MV}" "${EASYTLS_TEMP_DELETED}" "${inline_file}" || \
				die "Failed to restore: ${inline_file}"
		else
			"${EASYTLS_MV}" "${inline_file}-badhash" "${inline_file}" || \
				die "Failed to restore: ${inline_file}"
		fi
		# Always die
		die "Failed to update inline-index"
	fi

	if [ -z "${force_remove}" ]; then
		notice "Inline file removed: ${inline_file}"
	else
		notice "Inline file renamed to: ${inline_file}-badhash"
	fi
	easytls_verbose
} # => remove_inline ()

# Remove GROUP .inline file
remove_group_inline ()
{
	[ "$#" -ge 2 ] || die "Required option(s): <Client-Name> <Group-Name>"

	name="${1}"
	group="${2}"
	inline_file="${EASYTLS_PKI}/${name}-${group}.inline"

	[ "${TLSKEY_SUBNAME}" = 'NOSUBKEY' ] || {
		inline_file="${EASYTLS_PKI}/${name}-${TLSKEY_SUBNAME}.inline"
		}

	[ -f "${inline_file}" ] || missing_file "${inline_file}"

	# Inline serial
	x509_cert_serial "${inline_file}" || {
		error_msg "remove_group_inline - x509_cert_serial"
		return 1
		}

	# Use extracted_cert_serial
	inline_serial="${extracted_cert_serial}"

	unset -v force_remove
	# Check .inline HASH prior to removal
	if inline_file_verify_hash; then
		: # OK
	else
		unset -v error_log
		print "
* HELP: easytls can *Force* remove this file from the index.
        Once removed from the index, easytls will ignore this file.
        The file will be renamed to: ${inline_file}-badhash"
		confirm  "*Force* remove inline file: ${inline_file} ? " "yes" \
			"WARNING: Inline file HASH failed !"
		force_remove=1
	fi

	# Confirm remove
	[ -n "${force_remove}" ] || \
		confirm  "Remove inline file ? " "yes" "Remove: ${inline_file}"

	# Do not over write existing
	[ ! -f "${inline_file}-badhash" ] || \
		die "File exists: ${inline_file}-badhash"

	# Move
	if [ -z "${force_remove}" ]; then
		"${EASYTLS_MV}" "${inline_file}" "${EASYTLS_TEMP_DELETED}" || \
			die "Failed to move: ${inline_file}"
	else
		"${EASYTLS_MV}" "${inline_file}" "${inline_file}-badhash" || \
			die "Failed to move: ${inline_file}"
	fi

	# Update the index first
	if inline_index_update del; then
		# Remove
		if [ -z "${force_remove}" ]; then
			"${EASYTLS_RM}" "${EASYTLS_TEMP_DELETED}" || \
				die "Failed to remove: ${inline_file}"
		fi
	else
		# Undo move
		if [ -z "${force_remove}" ]; then
			"${EASYTLS_MV}" "${EASYTLS_TEMP_DELETED}" "${inline_file}" || \
				die "Failed to restore: ${inline_file}"
		else
			"${EASYTLS_MV}" "${inline_file}-badhash" "${inline_file}" || \
				die "Failed to restore: ${inline_file}"
		fi
		# Always die
		die "Failed to update inline-index"
	fi

	if [ -z "${force_remove}" ]; then
		notice "Inline file removed: ${inline_file}"
	else
		notice "Inline file renamed to: ${inline_file}-badhash"
	fi
	easytls_verbose
} # => remove_group_inline ()

# Rebuild inline index
inline_index_rebuild ()
{
	# DISABLED_DELETE
	# Temporarily disable
	warn "inline_index_rebuild is currently disabled."
	return 0
} # => inline_index_rebuild ()



############################################################################
#
# EASYTLS TLS-Crypt-V2 Key management Section
#

# Update tlskey-index
tlskey_index_update ()
{
	tk_action="${1}"

	# Verify tlskey_serial
	[ -n "${tlskey_serial}" ] || die "tlskey_index_update - tlskey_serial"

	# Update
	case "${tk_action}" in
	add)
		# Create the complete new_record
		new_record="${tlskey_serial} ${cert_serial} ${cli_name} ${TLSKEY_SUBNAME}"
		if universal_update add "${EASYTLS_TLSKEY_INDEX}" "${new_record}"; then
			: # ok
		else
			return 1
		fi
	;;
	del)
		# tlskey_serial is absolutely unique in tlskey-index
		old_record="${tlskey_serial}[[:blank:]]${cert_serial}[[:blank:]].*"
		if universal_update del "${EASYTLS_TLSKEY_INDEX}" "${old_record}"; then
			: # ok
		else
			return 1
		fi
	;;
	*)	error_msg "Unknown index action: ${tk_action}"
		return 1
	esac

	easytls_verbose "tlskey-index Update complete!"
	update_master_hash=1
} # => tlskey_index_update ()

# Verify current tlskey-index hash
tlskey_index_verify_hash ()
{
	# DISABLED_DELETE
	die "DISABLED tlskey_index_verify_hash"
	[ -z "${tlskey_index_verify_hash_block}" ] || \
		die "tlskey index verify hash must only run once"
	request_fixed_hash=1
	generate_and_match_valid_hash \
		"${EASYTLS_TLSKEY_INDEX}" "${EASYTLS_KEY_X_HASH}" || {
			error_msg "generate_and_match_valid_hash - tlskey_index_verify_hash"
			return 1
			}
	unset -v request_fixed_hash
	easytls_verbose "tlskey-index hash check OK"
	tlskey_index_verify_hash_block=1
} # => tlskey_index_verify_hash ()

# Save tlskey-index hash
tlskey_index_save_hash ()
{
	# DISABLED_DELETE
	die "DISABLED tlskey_index_save_hash"
	[ -z "${tlskey_index_save_hash_block}" ] || \
		die "tlskey index save hash must only run once"
	request_fixed_hash=1
	generate_and_save_file_hash \
		"${EASYTLS_TLSKEY_INDEX}" "${EASYTLS_KEY_X_HASH}" || {
			error_msg "generate_and_save_file_hash - tlskey_index_save_hash"
			return 1
			}
	unset -v request_fixed_hash
	easytls_verbose "tlskey-index hash save OK"
	update_master_hash=1
	tlskey_index_save_hash_block=1
} # => tlskey_index_save_hash ()

# Create Server TLSKEY serial number
tlskey_cv2_server_serial_number ()
{
	force_hash=1
	if generate_and_validate_file_hash "${1}"; then
		# hack off last 4 digits to differentiate server from client
		# Insert Group and Server marker
		if [ -z "${group_mode}" ]; then
			generated_valid_hash="SRV:${generated_valid_hash%%????}"
		else
			generated_valid_hash="G_S:${generated_valid_hash%%????}"
		fi
		server_TLSKEY_serial_number="${generated_valid_hash}"
	else
		die "tlskey_cv2_server_serial_number - generate_and_validate_file_hash"
	fi
	unset generated_valid_hash force_hash
} # => tlskey_cv2_server_serial_number ()

# Insert a serial number
tlskey_cv2_client_serial_number ()
{
	# Prepend random padding to metadata
	metadata_padding="$("${EASYRSA_OPENSSL}" rand -hex 4)" || {
		error_msg "tlskey_cv2_client_serial_number - metadata_padding"
		unset -v metadata_padding
		return 1
		}
	metadata="${metadata_padding}--${metadata}"

	# Generate and validate tlskey_serial from metadata string
	force_hash=1
	generate_and_validate_data_hash "${metadata}" || {
		error_msg "tlskey_cv2_client_serial_number - tlskey_serial"
		unset -v tlskey_serial
		return 1
		}
	# Use the hash
	tlskey_serial="${generated_valid_hash}"
	unset -v force_hash generated_valid_hash

	# Check for duplicate
	if "${EASYTLS_GREP}" -q "^${tlskey_serial}[[:blank:]]" \
		"${EASYTLS_TLSKEY_INDEX}"
	then
		error_msg "${EASYTLS_HASH_ALGO} duplicate found! *Please report this*"
		error_msg "It's your lucky day! You could win 10,000,000 Dollars"
		unset -v tlskey_serial
		return 1
	fi

	# Insert this valid hash as tlskey_serial into metadata
	metadata="${tlskey_serial}-${metadata}"
} # => tlskey_cv2_client_serial_number ()

# tlskey name subkeyname to tls serial
tlskey_index_fname_2_tlskser ()
{
	script="{if(\$3 \$4 ~ /^${name}${TLSKEY_SUBNAME}\$/) print \$1}"
	"${EASYTLS_AWK}" "${script}" "${EASYTLS_TLSKEY_INDEX}"
	unset -v script
} # => tlskey_index_fname_2_tlskser ()

# tlskey name subkeyname to x509 serial
tlskey_index_fname_2_x509ser ()
{
	script="{if(\$3 \$4 ~ /^${name}${TLSKEY_SUBNAME}\$/) print \$2}"
	"${EASYTLS_AWK}" "${script}" "${EASYTLS_TLSKEY_INDEX}"
	unset -v script
} # => tlskey_index_fname_2_x509ser ()

# tlskey verify serial number is valid
verify_cert_serial ()
{
	# DISABLED_DELETE - SOON
	# Test it
	[ -n "${1}" ] || {
		error_msg "verify_cert_serial - empty"
		return 1
		}

	[ "${1}" = "${1%[!0123456789abcdefABCDEF]*}" ] || {
		error_msg "verify_cert_serial - !Hex: ${1}"
		return 1
		}

	serial_size=${#1}

	# This test is invalid - random serial numbers are not always 32 hex
	# I could keep hashes of certificates (fingerprint) instead
	# That could tie-in to Openvpn peer fingerprint method
	# https://github.com/TinCanTech/easy-tls/issues/158
	case "${EASYRSA_RAND_SN}" in
	yes)
		[ "${serial_size}" -lt 41 ] || {
			error_msg "verify_cert_serial - cert_serial size !<41: ${cert_serial}"
			return 1
			}
	;;
	no)
		[ "${serial_size}" -lt 41 ] || {
			error_msg "verify_cert_serial - cert_serial size !<41: ${cert_serial}"
			return 1
			}
	;;
	*)
		error_msg "verify_cert_serial - Certificate style: ${EASYRSA_RAND_SN}"
		return 1
	esac
} # => verify_cert_serial ()

# tlskey verify serial number is valid
verify_tlskey_serial ()
{
	# DISABLED_DELETE
	die "verify_tlskey_serial DISABLED"
	# Test it
	[ "${tlskey_serial}" = "${tlskey_serial%[!0123456789abcdefABCDEF]*}" ] || {
			error_msg "verify_tlskey_serial - !Hex: ${tlskey_serial}"
			return 1
			}

	hash_size="${#tlskey_serial}"

	case "${EASYTLS_HASH_ALGO}" in
	SHA1)
		[ "${hash_size}" -eq 40 ] || {
			error_msg "verify_tlskey_serial - hash_size: ${tlskey_serial}"
			return 1
			}
	;;
	SHA256)
		[ "${hash_size}" -eq 64 ] || {
			error_msg "verify_tlskey_serial - hash_size size: ${tlskey_serial}"
			return 1
			}
	;;
	*)
		error_msg "verify_tlskey_serial - Unknown algorithm: ${EASYTLS_HASH_ALGO}"
		return 1
	esac
} # => verify_tlskey_serial ()

# # Verify that this key is no longer in use
verify_tlskey_in_use ()
{
	x509_patt=".*[[:blank:]]${cert_serial}[[:blank:]]"
	tlsk_patt=".*[[:blank:]]${tlskey_serial}"
	"${EASYTLS_GREP}" -q "^${x509_patt}${tlsk_patt}\$" "${EASYTLS_INLINE_INDEX}"
} # => verify_tlskey_in_use ()

# # Verify that this group key is no longer in use
verify_group_tlskey_in_use ()
{
	x509_patt=".*-${name}[[:blank:]]"
	tlsk_patt=".*[[:blank:]]${tlskey_serial}"
	"${EASYTLS_GREP}" "^${x509_patt}${tlsk_patt}\$" "${EASYTLS_INLINE_INDEX}"
} # => verify_group_tlskey_in_use ()

# Remove a TLS-Crypt-V2 Client key and update the tlskey-index
remove_tlskey ()
{
	[ "$#" -ge 1 ] || die "Required option(s): <filename_base>"

	name="${1}"
	if [ -n "${EASYTLS_NO_CA}" ]; then
		cert_file="${EASYTLS_PKI}/${name}.crt"
	else
		cert_file="${EASYRSA_PKI}/issued/${name}.crt"
	fi

	tlskey_file="${EASYTLS_PKI}/${name}-tls-crypt-v2.key"
	metadata_file="${EASYTLS_META_DATA_D}/${name}-tls-crypt-v2.metadata"

	[ "${TLSKEY_SUBNAME}" = 'NOSUBKEY' ] || {
		full_name="${name}-${TLSKEY_SUBNAME}"
		tlskey_file="${EASYTLS_PKI}/${full_name}-tls-crypt-v2.key"
		metadata_file="${EASYTLS_META_DATA_D}/${full_name}-tls-crypt-v2.metadata"
		}

	# Check required files (No metadata_file for servers)
	[ -f "${cert_file}" ] || missing_file "${cert_file}"
	[ -f "${tlskey_file}" ] || missing_file "${tlskey_file}"

	# Cert purpose
	if verify_cert_purpose "${cert_file}" 'SSL server : Yes'; then
		cert_purpose='server'
	elif verify_cert_purpose "${cert_file}" 'SSL client : Yes'; then
		cert_purpose='client'
	else
		error_msg "remove_tlskey - verify_cert_purpose failed"
		return 1
	fi

	if [ "${cert_purpose}" = 'client' ]; then
		[ -f "${metadata_file}" ] || missing_file "${metadata_file}"
	fi

	# Get tlskey serial number from tlskey-index
	tlskey_serial="$(tlskey_index_fname_2_tlskser)"

	# Verify this tlskey-serial
	validate_hash "${tlskey_serial}" || {
		error_msg "remove_tlskey - validate_hash: ${tlskey_serial}"
		unset -v tlskey_serial
		return 1
		}

	# Get x509 serial number from tlskey-index
	cert_serial="$(tlskey_index_fname_2_x509ser)" || \
		die "remove_tlskey - cert_serial"

	# Verify this cert-serial
	verify_cert_serial "${cert_serial}" || \
		die "remove_tlskey - verify_cert_serial"

	# Check this tlskey is not in use by an .inline file
	if verify_tlskey_in_use; then
		help_note="Remove the inline file which uses this key. See 'status inline'"
		die "This key is still in use: ${tlskey_file}"
	fi

	# Confirm remove
	confirm  "Remove tlskey file ? " "yes" "Remove: ${tlskey_file}"

	# Move
	"${EASYTLS_MV}" "${tlskey_file}" "${EASYTLS_TEMP_DELETED}" || \
		die "Failed to move: ${tlskey_file}"
	[ "${cert_purpose}" = 'server' ] || {
		"${EASYTLS_MV}" "${metadata_file}" "${metadata_file}-deleted" || \
			die "Failed to move: ${metadata_file}"
		}

	# Update index
	if tlskey_index_update del; then
		# Remove
		"${EASYTLS_RM}" "${EASYTLS_TEMP_DELETED}" || \
			die "Failed to remove: ${tlskey_file}"
		[ "${cert_purpose}" = 'server' ] || {
			"${EASYTLS_RM}" "${metadata_file}-deleted" || \
				die "Failed to remove: ${metadata_file}"
			}
	else
		# Undo move
		if [ -z "${force_remove}" ]; then
			"${EASYTLS_MV}" "${EASYTLS_TEMP_DELETED}" "${tlskey_file}" || \
				die "Failed to restore: ${tlskey_file}"
			[ "${cert_purpose}" = 'server' ] || {
				"${EASYTLS_MV}" "${metadata_file}-deleted" "${metadata_file}" || \
					die "Failed to restore: ${metadata_file}"
				}
		fi
		# Always die
		die "Failed to update tlskey-index"
	fi

	notice "TLS-Crypt-V2 key removed: ${tlskey_file}"
	easytls_verbose
} # => remove_tlskey ()

# Remove a TLS-Crypt-V2 Client key and update the tlskey-index
remove_group_tlskey ()
{
	[ "$#" -ge 1 ] || die "Required option(s): <filename_base>"

	name="${1}"

	tlskey_file="${EASYTLS_PKI}/${name}-tls-crypt-v2.key"
	metadata_file="${EASYTLS_META_DATA_D}/${name}-tls-crypt-v2.metadata"

	[ "${TLSKEY_SUBNAME}" = 'NOSUBKEY' ] || {
		full_name="${name}-${TLSKEY_SUBNAME}"
		tlskey_file="${EASYTLS_PKI}/${full_name}-tls-crypt-v2.key"
		metadata_file="${EASYTLS_META_DATA_D}/${full_name}-tls-crypt-v2.metadata"
		}

	# Check required files
	[ -f "${tlskey_file}" ] || missing_file "${tlskey_file}"
	[ -f "${metadata_file}" ] || missing_file "${metadata_file}"

	# Get tlskey serial number from tlskey-index
	tlskey_serial="$(tlskey_index_fname_2_tlskser)"

	# Verify this tlskey-serial
	validate_hash "${tlskey_serial}" || {
		error_msg "remove_tlskey - validate_hash failed: ${tlskey_serial}"
		unset -v tlskey_serial
		return 1
		}

	# Get x509 serial number from tlskey-index
	cert_serial="$(tlskey_index_fname_2_x509ser)" || \
		die "remove_tlskey - cert_serial"

	# Verify this cert-serial
	verify_cert_serial "${cert_serial}" || \
		die "remove_tlskey - verify_cert_serial"

	# Check this tlskey is not in use by an .inline file
	if verify_group_tlskey_in_use; then
		help_note="Remove the inline files which use this key. See 'status inline'"
		die "This key is still in use: ${tlskey_file}"
	fi

	# Confirm remove
	confirm  "Remove tlskey file ? " "yes" "Remove: ${tlskey_file}"

	# Move
	"${EASYTLS_MV}" "${tlskey_file}" "${EASYTLS_TEMP_DELETED}" || \
		die "Failed to move: ${tlskey_file}"
	[ "${cert_purpose}" = 'server' ] || {
		"${EASYTLS_MV}" "${metadata_file}" "${metadata_file}-deleted" || \
			die "Failed to move: ${metadata_file}"
		}

	# Update index
	if tlskey_index_update del; then
		# Remove
		"${EASYTLS_RM}" "${EASYTLS_TEMP_DELETED}" || \
			die "Failed to remove: ${tlskey_file}"
		[ "${cert_purpose}" = 'server' ] || {
			"${EASYTLS_RM}" "${metadata_file}-deleted" || \
				die "Failed to remove: ${metadata_file}"
			}
	else
		# Undo move
		if [ -z "${force_remove}" ]; then
			"${EASYTLS_MV}" "${EASYTLS_TEMP_DELETED}" "${tlskey_file}" || \
				die "Failed to restore: ${tlskey_file}"
			[ "${cert_purpose}" = 'server' ] || {
				"${EASYTLS_MV}" "${metadata_file}-deleted" "${metadata_file}" || \
					die "Failed to restore: ${metadata_file}"
				}
		fi
		# Always die
		die "Failed to update tlskey-index"
	fi

	notice "TLS-Crypt-V2 key removed: ${tlskey_file}"
	easytls_verbose
} # => remove_group_tlskey ()



############################################################################
#
# EASYTLS INLINE Section
#

# Renew .inline file
inline_renew ()
{
	# DISABLED_DELETE
	# Easy-RSA certificate renewal is broken, do not use it.
	error_msg "Easy-TLS does not support inline-renew."
	error_msg "Use: 'easytls inline-remove' to delete the old inline file"
	error_msg "and then create a new inline file for the new certificate."
	return 1
} # => inline_renew ()

# Get current inline key direction
inline_renew_key_direction ()
{
	# DISABLED_DELETE
	die "inline_renew_key_direction - DISABLED"
	"${EASYTLS_GREP}" '^key-direction [01]$' "${inline_file}" | \
		"${EASYTLS_AWK}" '{print $2}'
} # => inline_renew_key_direction ()

# Create inline credentials file from Easy-RSA PKI
inline_base ()
{
	[ "$#" -ge 1 ] || {
		error_msg "Required option(s): <filename_base>"
		return 1
		}

	name="${1}"
	shift

	# No options processing because 'no-key' and 'add-dh' are set by caller

	if [ -n "${EASYTLS_NO_CA}" ]; then
		cert_file="${EASYTLS_PKI}/${name}.crt"
		key_file="${EASYTLS_PKI}/${name}.key"
		# Always generate the fingerprint
	else
		ca_file="${EASYRSA_PKI}/ca.crt"
		cert_file="${EASYRSA_PKI}/issued/${name}.crt"
		key_file="${EASYRSA_PKI}/private/${name}.key"
	fi

	if [ -f "${cert_file}" ]; then
		# Cert fingerprint
		easytls_ssl_generate_fingerprint "${cert_file}" || {
			error_msg "inline_base - easytls_ssl_generate_fingerprint"
			return 1
			}
		cert_fpr="${certificate_fingerPrint}"
		unset -v certificate_fingerPrint

		# Cert purpose
		if verify_cert_purpose "${cert_file}" 'SSL server : Yes'; then
			cert_purpose='server'
		elif verify_cert_purpose "${cert_file}" 'SSL client : Yes'; then
			cert_purpose='client'
		else
			error_msg "inline_base - verify_cert_purpose failed"
			return 1
		fi
	else
		error_msg "Certificate file missing or revoked: ${cert_file}"
		return 1
	fi

	if [ -n "${no_x509_key}" ]; then
		: # Key file is not required
	else
		# Key file is required
		[ -f "${key_file}" ] || {
			error_msg "Key file missing or revoked: ${key_file}"
			return 1
			}
	fi

	if [ -n "${inline_dh_file}" ] && [ -z "${EASYTLS_NO_CA}" ]; then
		# dh file is required
		default_dh_file="${EASYRSA_PKI}/dh.pem"
		EASYRSA_DH_FILE="${EASYRSA_DH_FILE:-"${default_dh_file}"}"
		[ -f "${EASYRSA_DH_FILE}" ] || {
			help_note="Use --dh=<file_name> to specify an alternate dh file."
			error_msg "Diffie-Hellman parameters file missing: ${EASYRSA_DH_FILE}"
			return 1
			}
	fi

	# get the serial number of the certificate from OpenSSL
	x509_cert_serial "${cert_file}" || \
		die "inline_base - x509_cert_serial"

	# Use extracted_cert_serial
	cert_serial="${extracted_cert_serial}"

	# Build .base file
	{
		# Header
		"${EASYTLS_PRINTF}" '%s\n' "# ${TLSKEY_CUSTOM_GRP}"
		"${EASYTLS_PRINTF}" '%s\n' "# EasyTLS version ${EASYTLS_VERSION}"
		"${EASYTLS_PRINTF}" '%s\n' "# Common name: ${name}"
		"${EASYTLS_PRINTF}" '%s\n' "# X509 serial: ${cert_serial}"

		# Fingerprint
		[ -z "${EASYTLS_NO_CA}" ] || "${EASYTLS_PRINTF}" '%s\n\n' \
			"# Send this to your peer - Fingerprint: ${cert_fpr}"

		# Certificate
		"${EASYTLS_PRINTF}" '%s\n' "<cert>"
		"${EASYTLS_CAT}" "${cert_file}"
		"${EASYTLS_PRINTF}" '%s\n\n' "</cert>"

		# Key
		"${EASYTLS_PRINTF}" '%s\n' "<key>"

		if [ -n "${no_x509_key}" ]; then
			"${EASYTLS_PRINTF}" '%s\n' \
			'# * Replace this line with your complete x509 key file *'
		else
			"${EASYTLS_CAT}" "${key_file}"
		fi

		"${EASYTLS_PRINTF}" '%s\n\n' "</key>"

		# CA
		[ -n "${EASYTLS_NO_CA}" ] || {
			"${EASYTLS_PRINTF}" '%s\n' "<ca>"
			"${EASYTLS_CAT}" "${ca_file}"
			"${EASYTLS_PRINTF}" '%s\n\n' "</ca>"
			}

		# DH
		if [ -n "${inline_dh_file}" ] && [ -z "${EASYTLS_NO_CA}" ]; then
			"${EASYTLS_PRINTF}" '%s\n' "<dh>"
			"${EASYTLS_CAT}" "${EASYRSA_DH_FILE}"
			"${EASYTLS_PRINTF}" '%s\n\n' "</dh>"
		else
			[ "${cert_purpose}" = "client" ] || \
				"${EASYTLS_PRINTF}" '%s\n\n' "dh none"
		fi

	} > "${inline_base}" || {
		error_msg "Failed to write inline file: ${inline_base}"
		return 1
		}

	easytls_verbose "Inline base file created: ${inline_base}"
} # => inline_base ()

# Append TLS auth file to base file
inline_tls_auth ()
{
	[ "$#" -ge 1 ] || die "Required option(s): <filename_base>"

	name="${1}"
	shift

	unset -v key_direction no_x509_key inline_dh_file
	while :; do
		case "${1}" in
			0|1)
				key_direction="${1}" ;;
			no-key)
				no_x509_key=1 ;;
			add-dh)
				inline_dh_file=1 ;;
			'')
				# Ignore empty values
				: ;;
			*)
				die "Unknown command option: '${1}'"
		esac
		[ $# = 0 ] && break
		shift
	done

	tlskey_file="${EASYTLS_PKI}/tls-auth.key"
	inline_file="${EASYTLS_PKI}/${name}.inline"
	inline_temp="${EASYTLS_PKI}/${name}.temp"
	inline_base="${EASYTLS_PKI}/${name}.base"

	# Check inline file does not exist
	if [ -f "${inline_file}" ]; then
		if "${EASYTLS_GREP}" -q \
			'# Easy-TLS self-signed certificate base-inline file' \
			"${inline_file}"
		then
			# This file can be over-writen
			confirm "Are you sure you want to over-write the file? " "yes" \
				"This base-inline-file already exists: ${inline_file}"
		else
			help_note="Use 'easytls remove' to delete the old inline file."
			die "Inline file already exists: ${inline_file}"
		fi
	fi

	# Check tls-auth key exists
	[ -f "${tlskey_file}" ] || {
		help_note="Use 'easytls build' to create a TLS-Auth key."
		die "TLS key file does not exist: ${tlskey_file}"
		}

	# Inline base file
	inline_base "${name}" || die "Failed to create inline base file"

	# Determine key_direction
	case "${key_direction}" in
	0|1) : ;; # User specified --key-direction
	*)
		# Use defaults - cert_purpose from inline_base()
		case "${cert_purpose}" in
		[sS]erver)	key_direction=0 ;;
		[cC]lient)	key_direction=1 ;;
		*)			die "key-direction is required! Server=0, client=1."
		esac
	esac

	{	# Add key
		"${EASYTLS_PRINTF}" "%s\n\n" "# TLS auth"
		"${EASYTLS_PRINTF}" "%s\n\n" "key-direction ${key_direction}"
		"${EASYTLS_PRINTF}" "%s\n" "<tls-auth>"
		"${EASYTLS_CAT}" "${tlskey_file}"
		"${EASYTLS_PRINTF}" "%s\n\n" "</tls-auth>"

		# Share fingerprint template
		inline_share_fingerprint || \
			die "inline_tls_auth - inline_share_fingerprint"
	} > "${inline_temp}" || die "Failed to create inline file: ${inline_temp}"

	# Use cat for its intended purpose
	"${EASYTLS_CAT}" "${inline_base}" "${inline_temp}" > "${inline_file}"

	# Remove temp files
	"${EASYTLS_RM}" -f "${inline_base}" "${inline_temp}"

	# Hash inline file and add hash to index
	# TLS-Crypt-V1 do not have a serial so use preset
	tlskey_serial="${fixed_tls_auth_serial}"
	if inline_index_update add; then
		: # OK
	else
		die "inline_tls_auth - inline_index_update add"
	fi

	# share this client FP with server defined by -r=<serv-name> option
	inline_share_fingerprint "${name}" || die "Failed to share fingerprint"

	notice "Inline TLS auth file created: ${inline_file}"
	update_master_hash=1
	easytls_verbose
} # => inline_tls_auth ()

# Append TLS crypt file to base file
inline_tls_crypt_v1 ()
{
	[ "$#" -ge 1 ] || die "Required option(s): <filename_base>"

	name="${1}"
	shift

	unset -v no_x509_key inline_dh_file
	while :; do
		case "${1}" in
			no-key)
				no_x509_key=1 ;;
			add-dh)
				inline_dh_file=1 ;;
			'')
				# Ignore empty values
				: ;;
			*)
				die "Unknown command option: '${1}'"
		esac
		[ $# = 0 ] && break
		shift
	done

	tlskey_file="${EASYTLS_PKI}/tls-crypt.key"
	inline_file="${EASYTLS_PKI}/${name}.inline"
	inline_temp="${EASYTLS_PKI}/${name}.temp"
	inline_base="${EASYTLS_PKI}/${name}.base"

	# Check inline file does not exist
	if [ -f "${inline_file}" ]; then
		if "${EASYTLS_GREP}" -q \
			'# Easy-TLS self-signed certificate base-inline file' \
			"${inline_file}"
		then
			# This file can be over-writen
			confirm "Are you sure you want to over-write the file? " "yes" \
				"This base-inline-file already exists: ${inline_file}"
		else
			help_note="Use 'easytls remove' to delete the old inline file."
			die "Inline file already exists: ${inline_file}"
		fi
	fi

	# Check tls-crypt key exists
	[ -f "${tlskey_file}" ] || {
		help_note="Use 'easytls build' to create a TLS-Crypt-V1 key."
		die "TLS key file does not exist: ${tlskey_file}"
		}

	# Inline base file
	inline_base "${name}" || die "Failed to create inline base file"

	{	# Add key
		"${EASYTLS_PRINTF}" "%s\n\n" "# TLS crypt"
		"${EASYTLS_PRINTF}" "%s\n" "<tls-crypt>"
		"${EASYTLS_CAT}" "${tlskey_file}"
		"${EASYTLS_PRINTF}" "%s\n\n" "</tls-crypt>"

		# Share fingerprint template
		inline_share_fingerprint || \
			die "inline_tls_crypt_v1 - inline_share_fingerprint"

	} > "${inline_temp}" || die "Failed to create inline file: ${inline_temp}"

	# Use cat for its intended purpose
	"${EASYTLS_CAT}" "${inline_base}" "${inline_temp}" > "${inline_file}"

	# Remove temp files
	"${EASYTLS_RM}" -f "${inline_base}" "${inline_temp}"

	# Hash inline file and add hash to index
	# TLS-Crypt-V1 do not have a serial so use preset
	tlskey_serial="${fixed_tls_cryptv1_serial}"
	if inline_index_update add; then
		: # OK
	else
		die "inline_tls_crypt_v1 - inline_index_update add"
	fi

	# share this client FP with server defined by -r=<serv-name> option
	inline_share_fingerprint "${name}" || die "Failed to share fingerprint"

	notice "Inline TLS crypt file created: ${inline_file}"
	update_master_hash=1
	easytls_verbose
} # => inline_tls_crypt_v1 ()

# Append TLS crypt v2 file to base file
inline_tls_crypt_v2 ()
{
	[ "$#" -ge 1 ] || die "Required option(s): <filename_base>"

	name="${1}"
	shift

	unset -v no_x509_key inline_dh_file add_hardware no_metadata
	while :; do
		case "${1}" in
			no-key)
				no_x509_key=1 ;;
			add-dh)
				inline_dh_file=1 ;;
			add-ip|add-hw|add-hardware)
				add_hardware=1 ;;
			no-md|no-metadata)
				no_metadata=1 ;;
			'')
				# Ignore empty values
				: ;;
			*)
				die "Unknown command option: '${1}'"
		esac
		[ $# = 0 ] && break
		shift
	done

	cert_file="${EASYRSA_PKI}/issued/${name}.crt"
	[ -z "${EASYTLS_NO_CA}" ] || cert_file="${EASYTLS_PKI}/${name}.crt"
	[ -f "${cert_file}" ] || missing_file "${cert_file}"

	# Cert purpose
	if verify_cert_purpose "${cert_file}" 'SSL server : Yes'; then
		cert_purpose='server'
	elif verify_cert_purpose "${cert_file}" 'SSL client : Yes'; then
		cert_purpose='client'
	else
		error_msg "inline_tls_crypt_v2 - verify_cert_purpose failed"
		return 1
	fi

	# Set file names
	tlskey_file="${EASYTLS_PKI}/${name}-tls-crypt-v2.key"
	inline_file="${EASYTLS_PKI}/${name}.inline"
	inline_temp="${EASYTLS_PKI}/${name}.temp"
	inline_base="${EASYTLS_PKI}/${name}.base"
	metadata_file="${EASYTLS_META_DATA_D}/${name}-tls-crypt-v2.metadata"
	fpr_file="${EASYTLS_PKI}/${name}.fpr"

	sub_name="${name}-${TLSKEY_SUBNAME}"
	[ "${TLSKEY_SUBNAME}" = 'NOSUBKEY' ] || {
		tlskey_file="${EASYTLS_PKI}/${sub_name}-tls-crypt-v2.key"
		metadata_file="${EASYTLS_META_DATA_D}/${sub_name}-tls-crypt-v2.metadata"
		inline_file="${EASYTLS_PKI}/${sub_name}.inline"
		}

	# Check inline file does not exist
	if [ -f "${inline_file}" ]; then
		if "${EASYTLS_GREP}" -q \
			'# Easy-TLS self-signed certificate base-inline file' \
			"${inline_file}"
		then
			# This file can be over-writen
			confirm "Are you sure you want to over-write the file? " "yes" \
				"This base-inline-file already exists: ${inline_file}"
		else
			help_note="Use 'easytls remove' to delete the old inline file."
			die "Inline file already exists: ${inline_file}"
		fi
	fi

	# Check tls-crypt key exists
	[ -f "${tlskey_file}" ] || {
		help_note="Use 'easytls build' to create a TLS-Crypt-V2 key."
		die "TLS key file does not exist: ${tlskey_file}"
		}

	if [ "${cert_purpose}" = 'client' ]; then
		# Must be a client
		[ -f "${metadata_file}" ] || missing_file "${metadata_file}"

		# Get metadata
		metadata_string="$("${EASYTLS_CAT}" "${metadata_file}")" || \
			die "Failed to read metadata_file: ${metadata_file}"

		# Convert metadata string to variables
		metadata_stov_safe  "$metadata_string" || \
			die "metadata_stov_safe" 87

		# Set creation Date
		MD_longdate="${local_date_ascii}"

		# Verify Custom-Group
		[ "${TLSKEY_CUSTOM_GRP}" = "${MD_CUSTOM_G}" ] || {
			help_note="This key was built with a different Custom-Group"
			die "Custom-group mismatch: ${TLSKEY_CUSTOM_GRP} <> ${MD_CUSTOM_G}"
			}

		if [ -n "${EASYTLS_NO_CA}" ]; then
			# Get the server serial number
			cert_file="${EASYTLS_PKI}/${MD_SRV_NAME}.crt"
			x509_cert_serial "${cert_file}" || \
				die "inline_tls_crypt_v2 - x509_cert_serial - ${cert_file}"

			# Use extracted_cert_serial
			srv_serial="${extracted_cert_serial}"
		else
			# Get the CA serial number
			ca_cert="${EASYRSA_PKI}/ca.crt"
			ca_serial=
			x509_cert_serial "${ca_cert}" || \
				die "inline_tls_crypt_v2 - x509_cert_serial - ${ca_cert}"

			# Use extracted_cert_serial
			ca_serial="${extracted_cert_serial}"
		fi
	else
		# Must be a server
		no_metadata=1
		tlskey_cv2_server_serial_number "${tlskey_file}"
		tlskey_serial="${server_TLSKEY_serial_number}"
		MD_TLSKEY_SERIAL="${tlskey_serial}"
	fi

	# Inline base file
	inline_base "${name}" || die "Failed to create inline base file"

	# Append TLS-Crypt-V2 key
	{
		"${EASYTLS_PRINTF}" "%s\n" \
			"# metadata Easy-TLS-version ${EASYTLS_VERSION} - TLS-Crypt-v2 key"

		if [ -n "${no_metadata}" ]; then
			# If this is a defined sub-key name then add the name anyway
			[ "${MD_SUBKEY}" = 'NOSUBKEY' ] || "${EASYTLS_PRINTF}" '%s\n\n' \
				"# metadata Sub-key-name: ${MD_SUBKEY}"
		else
			if [ -n "${EASYTLS_NO_CA}" ]; then
				"${EASYTLS_PRINTF}" '%s\n' \
					"# metadata Server-serial: ${srv_serial}"
			else
				"${EASYTLS_PRINTF}" '%s\n' "# metadata CA-serial: ${ca_serial}"
			fi

			"${EASYTLS_PRINTF}" '%s\n' \
				"# metadata tlskey-serial: ${MD_TLSKEY_SERIAL}"
			"${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Creation-Date: ${MD_longdate}"
			"${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Custom-Group: ${MD_CUSTOM_G}"
			"${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Server-Common-Name: ${MD_SRV_NAME}"
			"${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Client-Common-Name: ${MD_NAME}"

			[ "${MD_SUBKEY}" = 'NOSUBKEY' ] || "${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Sub-key-name: ${MD_SUBKEY}"

			[ "${MD_OPT}" = "OPT" ] || "${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Opt: ${MD_OPT}"

			"${EASYTLS_PRINTF}" '%s' "# metadata Key-status: "
			if [ "${MD_FILTERS}" = "=000000000000=" ]; then
				"${EASYTLS_PRINTF}" '%s\n' "Open"
			else
				if [ -n "${add_hardware}" ]; then
					"${EASYTLS_PRINTF}" '%s\n' "Locked ${MD_FILTERS}"
				else
					"${EASYTLS_PRINTF}" '%s\n' "Closed"
				fi
			fi
		fi

		# push-peer-info
		if [ "${cert_purpose}" = 'client' ]; then
			"${EASYTLS_PRINTF}" '\n%s\n' \
				"setenv UV_TLSKEY_SERIAL ${MD_TLSKEY_SERIAL}"
			"${EASYTLS_PRINTF}" "%s\n\n" "push-peer-info"
		fi

		# Add key
		"${EASYTLS_PRINTF}" "%s\n" "<tls-crypt-v2>"
		"${EASYTLS_CAT}" "${tlskey_file}"
		"${EASYTLS_PRINTF}" "%s\n\n" "</tls-crypt-v2>"

		# Share fingerprint template
		inline_share_fingerprint || \
			die "inline_tls_crypt_v2 - inline_share_fingerprint"

	} > "${inline_temp}" || die "Failed to create inline file: ${inline_temp}"

	# Use cat for its intended purpose
	"${EASYTLS_CAT}" "${inline_base}" "${inline_temp}" > "${inline_file}"

	# Remove temp files
	rm -f "${inline_base}" "${inline_temp}"

	# Hash inline file and add hash to index
	tlskey_serial="${MD_TLSKEY_SERIAL}"
	if inline_index_update add; then
		: # OK
	else
		die "inline_tls_crypt_v2 - inline_index_update add"
	fi

	# share this client FP with server defined by -r=<serv-name> option
	inline_share_fingerprint "${name}" || die "Failed to share fingerprint"

	notice "Inline TLS crypt v2 ${cert_purpose} file created: ${inline_file}"
	easytls_verbose
} # => inline_tls_crypt_v2 ()

# Inline TLS-Crypt-V2 Group Server Key with X509 Certificate
# shellcheck disable=SC2154
inline_tls_cv2_group_server ()
{
	[ "$#" -ge 2 ] || \
		die "Required option(s): <server_common_name> <server_group_key>"

	name="${1}"
	shift

	group_key="${1}"
	shift

	file_name_stub="${name}-${group_key}"

	unset -v no_x509_key inline_dh_file
	while :; do
		case "${1}" in
			no-key)
				no_x509_key=1 ;;
			add-dh)
				inline_dh_file=1 ;;
			'')
				# Ignore empty values
				: ;;
			*)
				die "Unknown command option: '${1}'"
		esac
		[ $# = 0 ] && break
		shift
	done

	cert_file="${EASYRSA_PKI}/issued/${name}.crt"
	[ -z "${EASYTLS_NO_CA}" ] || cert_file="${EASYTLS_PKI}/${name}.crt"
	[ -f "${cert_file}" ] || missing_file "${cert_file}"

	# Cert purpose
	if verify_cert_purpose "${cert_file}" 'SSL server : Yes'; then
		cert_purpose='server'
	else
		die "Require server certificate."
	fi

	# Set file names
	tlskey_file="${EASYTLS_PKI}/${group_key}-tls-crypt-v2.key"
	inline_file="${EASYTLS_PKI}/${file_name_stub}.inline"
	inline_temp="${EASYTLS_PKI}/${file_name_stub}.temp"
	inline_base="${EASYTLS_PKI}/${file_name_stub}.base"
	metadata_file="${EASYTLS_META_DATA_D}/${group_key}-tls-crypt-v2.metadata"
	fpr_file="${EASYTLS_PKI}/${name}.fpr"

	sub_name="${name}-${TLSKEY_SUBNAME}"
	[ "${TLSKEY_SUBNAME}" = 'NOSUBKEY' ] || {
		# Temporarily disable this
		die "TLS-Crypt-V2 Client Group Keys do not support --subkey"
		tlskey_file="${EASYTLS_PKI}/${sub_name}-tls-crypt-v2.key"
		metadata_file="${EASYTLS_META_DATA_D}/${sub_name}-tls-crypt-v2.metadata"
		#inline_file="${EASYTLS_PKI}/${sub_name}-gc.inline"
		inline_file="${EASYTLS_PKI}/${sub_name}.inline"
		}

	# Check inline file does not exist
	if [ -f "${inline_file}" ]; then
		if "${EASYTLS_GREP}" -q \
			'# Easy-TLS self-signed certificate base-inline file' \
			"${inline_file}"
		then
			# This file can be over-writen
			confirm "Are you sure you want to over-write the file? " "yes" \
				"This base-inline-file already exists: ${inline_file}"
		else
			help_note="Use 'easytls remove' to delete the old inline file."
			die "Inline file already exists: ${inline_file}"
		fi
	fi

	# Check tls-crypt key exists
	[ -f "${tlskey_file}" ] || {
		help_note="Use 'easytls build' to create a TLS-Crypt-V2 key."
		die "TLS key file does not exist: ${tlskey_file}"
		}

	# Settings for Server
	no_metadata=1
	tlskey_cv2_server_serial_number "${tlskey_file}"
	tlskey_serial="${server_TLSKEY_serial_number}"
	MD_TLSKEY_SERIAL="${tlskey_serial}"

	# Inline base file
	inline_base "${name}" || die "Failed to create inline base file"

	# Append TLS-Crypt-V2 key
	{
		"${EASYTLS_PRINTF}" '%s\n%s\n' \
			"# metadata Easy-TLS-version ${EASYTLS_VERSION}" \
			"# metadata TLS-Crypt-v2 GROUP Server Key: ${group_key}"

		if [ -n "${no_metadata}" ]; then
			# If this is a defined sub-key name then add the name anyway
			if [ "${cert_purpose}" = 'client' ]; then
				[ "${MD_subkey}" = 'NOSUBKEY' ] || "${EASYTLS_PRINTF}" \
					'%s\n\n' "# metadata Sub-key-name: ${MD_subkey}"
			fi
		else
			if [ -n "${EASYTLS_NO_CA}" ]; then
				"${EASYTLS_PRINTF}" '%s\n' \
					"# metadata Server-serial: ${srv_serial}"
			else
				"${EASYTLS_PRINTF}" '%s\n' "# metadata CA-serial: ${ca_serial}"
			fi

			"${EASYTLS_PRINTF}" '%s\n' \
				"# metadata tlskey-serial: ${MD_TLSKEY_SERIAL}"
			"${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Creation-Date: ${MD_date}"
			"${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Custom-Group: ${MD_cgroup}"
			"${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Server-Common-Name: ${MD_SRV_NAME}"
			"${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Client-Common-Name: ${MD_cn}"

			[ "${MD_subkey}" = 'NOSUBKEY' ] || "${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Sub-key-name: ${MD_subkey}"

			[ "${MD_opt}" = "OPT" ] || "${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Opt: ${MD_opt}"

			"${EASYTLS_PRINTF}" '%s' "# metadata Key-status: "
			if [ "${MD_hw}" = "=000000000000=" ]; then
				"${EASYTLS_PRINTF}" '%s\n' "Open"
			else
				if [ -n "${add_hardware}" ]; then
					"${EASYTLS_PRINTF}" '%s\n' "Locked ${MD_hw}"
				else
					"${EASYTLS_PRINTF}" '%s\n' "Closed"
				fi
			fi
		fi

		# push-peer-info
		if [ "${cert_purpose}" = 'client' ]; then
			"${EASYTLS_PRINTF}" '\n%s\n' \
				"setenv UV_TLSKEY_SERIAL ${MD_TLSKEY_SERIAL}"
			"${EASYTLS_PRINTF}" "%s\n\n" "push-peer-info"
		fi

		# Add key
		"${EASYTLS_PRINTF}" "%s\n" "<tls-crypt-v2>"
		"${EASYTLS_CAT}" "${tlskey_file}"
		"${EASYTLS_PRINTF}" "%s\n\n" "</tls-crypt-v2>"

		# Share fingerprint template
		inline_share_fingerprint || \
			die "inline_tls_cv2_group_server - inline_share_fingerprint"

	} > "${inline_temp}" || die "Failed to create inline file: ${inline_temp}"

	# Use cat for its intended purpose
	"${EASYTLS_CAT}" "${inline_base}" "${inline_temp}" > "${inline_file}"

	# Remove temp files
	rm -f "${inline_base}" "${inline_temp}"

	# Hash inline file and add hash to index
	save_name="${name}"
	name="${file_name_stub}"
	tlskey_serial="${MD_TLSKEY_SERIAL}"
	if inline_index_update add; then
		: # OK
	else
		die "inline_tls_crypt_v2_group_server - inline_index_update add"
	fi

	name="${save_name}"
	unset -v save_name

	# share this client FP with server defined by -r=<serv-name> option
	inline_share_fingerprint "${name}" || die "Failed to share fingerprint"

	notice "TLS-Crypt-V2 GROUP Server Inline file created: ${inline_file}"
	easytls_verbose
} # => inline_tls_crypt_v2_group_server ()

# Inline TLS-Crypt-V2 Group Client Key with X509 Certificate
inline_tls_cv2_group_client ()
{
	[ "$#" -ge 2 ] || \
		die "Required option(s): <client_common_name> <client_group_key>"

	name="${1}"
	shift

	group_key="${1}"
	shift

	file_name_stub="${name}-${group_key}"

	unset -v no_x509_key add_hardware no_metadata
	while :; do
		case "${1}" in
			no-key)
				no_x509_key=1 ;;
			add-hw)
				add_hardware=1 ;;
			no-md)
				no_metadata=1 ;;
			'')
				# Ignore empty values
				: ;;
			*)
				die "Unknown command option: '${1}'"
		esac
		[ $# = 0 ] && break
		shift
	done

	cert_file="${EASYRSA_PKI}/issued/${name}.crt"
	[ -z "${EASYTLS_NO_CA}" ] || cert_file="${EASYTLS_PKI}/${name}.crt"
	[ -f "${cert_file}" ] || missing_file "${cert_file}"

	# Cert purpose
	if verify_cert_purpose "${cert_file}" 'SSL client : Yes'; then
		cert_purpose='client'
	else
		die "Require client certificate."
	fi

	# Set file names
	tlskey_file="${EASYTLS_PKI}/${group_key}-tls-crypt-v2.key"
	inline_file="${EASYTLS_PKI}/${file_name_stub}.inline"
	inline_temp="${EASYTLS_PKI}/${file_name_stub}.temp"
	inline_base="${EASYTLS_PKI}/${file_name_stub}.base"
	metadata_file="${EASYTLS_META_DATA_D}/${group_key}-tls-crypt-v2.metadata"
	fpr_file="${EASYTLS_PKI}/${name}.fpr"

	sub_name="${name}-${TLSKEY_SUBNAME}"
	[ "${TLSKEY_SUBNAME}" = 'NOSUBKEY' ] || {
		# Temporarily disable this
		die "TLS-Crypt-V2 Client Group Keys do not support --subkey"
		tlskey_file="${EASYTLS_PKI}/${sub_name}-tls-crypt-v2.key"
		metadata_file="${EASYTLS_META_DATA_D}/${sub_name}-tls-crypt-v2.metadata"
		inline_file="${EASYTLS_PKI}/${sub_name}-gc.inline"
		}

	# Check inline file does not exist
	if [ -f "${inline_file}" ]; then
		if "${EASYTLS_GREP}" -q \
			'# Easy-TLS self-signed certificate base-inline file' \
			"${inline_file}"
		then
			# This file can be over-writen
			confirm "Are you sure you want to over-write the file? " "yes" \
				"This base-inline-file already exists: ${inline_file}"
		else
			help_note="Use 'easytls remove' to delete the old inline file."
			die "Inline file already exists: ${inline_file}"
		fi
	fi

	# Check tls-crypt key exists
	[ -f "${tlskey_file}" ] || {
		help_note="Use 'easytls build' to create a TLS-Crypt-V2 key."
		die "TLS key file does not exist: ${tlskey_file}"
		}

		# Must be a client
		[ -f "${metadata_file}" ] || missing_file "${metadata_file}"

		# Get metadata
		metadata_string="$("${EASYTLS_CAT}" "${metadata_file}")" || \
			die "Failed to read metadata_file: ${metadata_file}"

		# Convert metadata string to variables
		metadata_stov_safe  "$metadata_string" || \
			die "metadata_stov_safe" 87

		# Set creation Date
		MD_longdate="${local_date_ascii}"

		# Verify Custom-Group
		[ "${TLSKEY_CUSTOM_GRP}" = "${MD_CUSTOM_G}" ] || {
			help_note="This key was built with a different Custom-Group"
			die "Custom-group mismatch: ${TLSKEY_CUSTOM_GRP} <> ${MD_CUSTOM_G}"
			}

		if [ -n "${EASYTLS_NO_CA}" ]; then
			# Get the server serial number
			cert_file="${EASYTLS_PKI}/${MD_SRV_NAME}.crt"
			x509_cert_serial "${cert_file}" || \
				die "inline_tls_cv2_group_client - x509_cert_serial: ${cert_file}"

			# Use extracted_cert_serial
			srv_serial="${extracted_cert_serial}"
		else
			# Get the CA serial number
			ca_cert="${EASYRSA_PKI}/ca.crt"
			ca_serial=
			x509_cert_serial "${ca_cert}" || \
				die "inline_tls_cv2_group_client - x509_cert_serial: ${ca_cert}"

			# Use extracted_cert_serial
			ca_serial="${extracted_cert_serial}"
		fi

	# Inline base file
	inline_base "${name}" || die "Failed to create inline base file"

	# Append TLS-Crypt-V2 key
	{
		"${EASYTLS_PRINTF}" '%s\n%s\n' \
			"# metadata Easy-TLS-version ${EASYTLS_VERSION}" \
			"# metadata TLS-Crypt-v2 GROUP Client Key: ${group_key}"

		if [ -n "${no_metadata}" ]; then
			# If this is a defined sub-key name then add the name anyway
			[ "${MD_SUBKEY}" = 'NOSUBKEY' ] || "${EASYTLS_PRINTF}" '%s\n\n' \
				"# metadata Sub-key-name: ${MD_SUBKEY}"
		else
			if [ -n "${EASYTLS_NO_CA}" ]; then
				"${EASYTLS_PRINTF}" '%s\n' \
					"# metadata Server-serial: ${srv_serial}"
			else
				"${EASYTLS_PRINTF}" '%s\n' "# metadata CA-serial: ${ca_serial}"
			fi

			"${EASYTLS_PRINTF}" '%s\n' \
				"# metadata tlskey-serial: ${MD_TLSKEY_SERIAL}"
			"${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Creation-Date: ${MD_longdate}"
			"${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Custom-Group: ${MD_CUSTOM_G}"
			"${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Server-Common-Name: ${MD_SRV_NAME}"
			"${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Client-Common-Name: ${MD_NAME}"

			[ "${MD_SUBKEY}" = 'NOSUBKEY' ] || "${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Sub-key-name: ${MD_SUBKEY}"

			[ "${MD_OPT}" = "OPT" ] || "${EASYTLS_PRINTF}" '%s\n' \
				"# metadata Opt: ${MD_OPT}"

			"${EASYTLS_PRINTF}" '%s' "# metadata Key-status: "
			if [ "${MD_FILTERS}" = "=000000000000=" ]; then
				"${EASYTLS_PRINTF}" '%s\n' "Open"
			else
				if [ -n "${add_hardware}" ]; then
					"${EASYTLS_PRINTF}" '%s\n' "Locked ${MD_FILTERS}"
				else
					"${EASYTLS_PRINTF}" '%s\n' "Closed"
				fi
			fi
		fi

		# push-peer-info
		if [ "${cert_purpose}" = 'client' ]; then
			"${EASYTLS_PRINTF}" '\n%s\n' \
				"setenv UV_TLSKEY_SERIAL ${MD_TLSKEY_SERIAL}"
			"${EASYTLS_PRINTF}" "%s\n\n" "push-peer-info"
		fi

		# Add key
		"${EASYTLS_PRINTF}" "%s\n" "<tls-crypt-v2>"
		"${EASYTLS_CAT}" "${tlskey_file}"
		"${EASYTLS_PRINTF}" "%s\n\n" "</tls-crypt-v2>"

		# Share fingerprint template
		inline_share_fingerprint || \
			die "inline_tls_cv2_group_client - inline_share_fingerprint"

	} > "${inline_temp}" || die "Failed to create inline file: ${inline_temp}"

	# Use cat for its intended purpose
	"${EASYTLS_CAT}" "${inline_base}" "${inline_temp}" > "${inline_file}"

	# Remove temp files
	rm -f "${inline_base}" "${inline_temp}"

	# Hash inline file and add hash to index
	save_name="${name}"
	name="${file_name_stub}"
	tlskey_serial="${MD_TLSKEY_SERIAL}"
	if inline_index_update add; then
		: # OK
	else
		die "inline_tls_crypt_v2_group_client - inline_index_update add"
	fi

	name="${save_name}"
	unset -v save_name

	# share this client FP with server defined by -r=<serv-name> option
	inline_share_fingerprint "${name}" || die "Failed to share fingerprint"

	notice "Inline TLS-Crypt-V2 GROUP Client file created: ${inline_file}"
	easytls_verbose
} # => inline_tls_crypt_v2_group_client ()

# Share peer fingerprints
inline_share_fingerprint ()
{
	# Only supported by No-CA mode
	# Normal CA mode ignores this step when building inline files
	[ -n "${EASYTLS_NO_CA}" ] || return 0

	# Keep count - Note: The first run is always silent
	isfp_count="$(( isfp_count + 1 ))"

	# When first creating this inline add FP <tags> and return
	if [ "$#" -eq 0 ]; then
		# Only add inline tags for fingerprint
		"${EASYTLS_PRINTF}" '%s\n' "<peer-fingerprint>"
		"${EASYTLS_PRINTF}" '%s\n' \
			" * Replace this line with your peer's fingerprint *"
		"${EASYTLS_PRINTF}" '%s\n' "</peer-fingerprint>"
		return 0
	fi

	# Verbose message
	easytls_verbose "Run inline_share_fingerprint (${isfp_count})"

	# If called by a user then require option name
	[ "$#" -gt 0 ] || die "Required option(s): <client_common_name>"

	# Only supported for client - This needs improvement
	if [ -n "${EASYTLS_PEER_FPR}" ] && {
		[ "${cert_purpose}" = "client" ] || [ "${cert_type}" = "Client" ]; }
	then
		# share fingerprints of server and client
		:
	else
		return 0
	fi

	# Client setup
	cli_name="${1}"
	cli_cert="${EASYTLS_PKI}/${cli_name}.crt"
	[ -f "${cli_cert}" ] || \
		missing_file "inline_share_fingerprint - ${cli_cert}"
	cli_inline="${EASYTLS_PKI}/${cli_name}.inline"
	cli_fp_list="${EASYTLS_DATA_DIR}/${cli_name}.pfp-list"

	# Set TLSKEY_SUBNAME names
	sub_name="${name}-${TLSKEY_SUBNAME}"
	[ "${TLSKEY_SUBNAME}" = 'NOSUBKEY' ] || {
		cli_inline="${EASYTLS_PKI}/${sub_name}.inline"
		cli_fp_list="${EASYTLS_PKI}/${sub_name}.pfp-list"
		}

	# File must exist
	[ -f "${cli_inline}" ] || \
		missing_file "inline_share_fingerprint - ${cli_inline}"

	# generate client FP
	validate_hash_block="$(( validate_hash_block - 1 ))"
	easytls_ssl_generate_fingerprint "${cli_cert}" || {
		error_msg "inline_share_fingerprint - generate client FP"
		return 1
		}
	# Use certificate_fingerPrint
	cli_fpr="${certificate_fingerPrint}"
	unset -v certificate_fingerPrint

	# Server files
	srv_name="${EASYTLS_PEER_FPR}"
	srv_cert="${EASYTLS_PKI}/${srv_name}.crt"
	srv_inline="${EASYTLS_PKI}/${srv_name}.inline"
	srv_fp_list="${EASYTLS_DATA_DIR}/${srv_name}.pfp-list"

	# server cert and inline must exist
	[ -f "${srv_cert}" ] || \
		missing_file "inline_share_fingerprint - ${srv_cert}"
	[ -f "${srv_inline}" ] || \
		missing_file "inline_share_fingerprint - ${srv_inline}"

	# generate server FP
	validate_hash_block="$(( validate_hash_block - 1 ))"
	easytls_ssl_generate_fingerprint "${srv_cert}" || {
		error_msg "inline_share_fingerprint - generate server FP"
		return 1
		}
	# Use certificate_fingerPrint
	srv_fpr="${certificate_fingerPrint}"
	unset -v certificate_fingerPrint

	# fingerprint pattern for regex match
	fpr_tplt=
	i=1
	while [ "${i}" -lt 32 ]; do
		fpr_tplt="${fpr_tplt}..:"
		i="$(( i + 1 ))"
	done
	fpr_tplt="${fpr_tplt}.."

	# client inline-file hash
	validate_hash_block="$(( validate_hash_block - 1 ))"
	generate_and_validate_file_hash "${cli_inline}" || {
		error_msg "inline_share_fingerprint - generate_and_validate_file_hash"
		error_msg "${cli_inline}"
		return 1
		}
	# Use the hash
	verified_inline_hash="${generated_valid_hash}"
	unset -v generated_valid_hash

	# check there is an inline record for this CLIENT
	if "${EASYTLS_GREP}" -q "${verified_inline_hash}" "${EASYTLS_INLINE_INDEX}"
	then
		# Remove client from inline index
		known_inline_hash="${verified_inline_hash}"
		inline_serial="$(inline_index_ilhash_to_serial)"

		# Must unset the usage block
		unset -v inline_index_save_hash_block
		validate_hash_block="$(( validate_hash_block - 1 ))"
		if inline_index_update del; then
			: # OK
		else
			die "inline_share_fingerprint - inline_index_update del"
		fi

	else
		#die "Why is client inline hash missing from index ?"
		# No-CA mode
		do_not_index_client_inline_file=1
	fi

	# Give server name and FP to client
	{	print "# Peer-Server ${srv_name}"
		print "${srv_fpr}"
	} > "${cli_fp_list}-temp" || die "inline_share_fingerprint - Peer-Server"
	"${EASYTLS_MV}" -f "${cli_fp_list}-temp" "${cli_fp_list}"

	# Delete existing
	"${EASYTLS_SED}" -i \
		-e "\\\^<peer-fingerprint>\$\\d" \
		-e "\\\^#[[:blank:]]Peer-Server.*\$\\d" \
		-e "\\\^.* Replace this line with your peer's fingerprint .*\$\\d" \
		-e "\\\^${fpr_tplt}\$\\d" \
		-e "\\\^</peer-fingerprint>\$\\d" "${cli_inline}" || \
			die "inline_share_fingerprint - Delete existing from CLIENT"

	# Append new server
	{	"${EASYTLS_CAT}" "${cli_inline}"
		print "<peer-fingerprint>"
		"${EASYTLS_CAT}" "${cli_fp_list}"
		print "</peer-fingerprint>"
	} > "${cli_inline}-temp" || die "inline_share_fingerprint - Append new server"
	"${EASYTLS_MV}" -f "${cli_inline}-temp" "${cli_inline}"

	# Add client to inline-index
	if [ -n "${do_not_index_client_inline_file}" ]; then
		: # Do not index client inline file
	else
		name="${cli_name}"
		inline_file="${cli_inline}"

		# Must unset the usage block
		unset -v inline_index_save_hash_block
		if inline_index_update add; then
			: # OK
		else
			die "inline_share_fingerprint - inline_index_update add"
		fi
	fi

	# server inline-file hash
	validate_hash_block="$(( validate_hash_block - 1 ))"
	generate_and_validate_file_hash "${srv_inline}" || {
		error_msg "generate_and_validate_file_hash - inline_share_fingerprint"
		error_msg "${srv_inline}"
		return 1
		}
	# Use the hash
	verified_inline_hash="${generated_valid_hash}"
	unset -v generated_valid_hash

	# check there is an inline record for this SERVER
	if "${EASYTLS_GREP}" -q "${verified_inline_hash}" "${EASYTLS_INLINE_INDEX}"
	then
		# Remove node from inline index
		known_inline_hash="${verified_inline_hash}"
		inline_serial="$(inline_index_ilhash_to_serial)"

		# Must unset the usage block
		unset -v inline_index_save_hash_block
		validate_hash_block="$(( validate_hash_block - 1 ))"
		if inline_index_update del; then
			: # OK
		else
			die "inline_share_fingerprint - inline_index_update del"
		fi

	else
		#die "Why is server inline hash missing from index ?"
		# No-CA mode
		do_not_index_server_inline_file=1
	fi

	# Add client name and FP to server FP list
	if [ -f "${srv_fp_list}" ] && \
		"${EASYTLS_GREP}" -q "${cli_fpr}" "${srv_fp_list}"
	then
		: # Fingerprint is already added
	else
		{	[ ! -f "${srv_fp_list}" ] || "${EASYTLS_CAT}" "${srv_fp_list}"
			print "# Peer-Client ${cli_name}"
			print "${cli_fpr}"
		} > "${srv_fp_list}-temp" || die "inline_share_fingerprint - Add client"
		"${EASYTLS_MV}" -f "${srv_fp_list}-temp" "${srv_fp_list}"
	fi

	# Update server peer-fingerprint list in inline-file
	# Delete existing
	"${EASYTLS_SED}" -i \
		-e "\\\^<peer-fingerprint>\$\\d" \
		-e "\\\^#[[:blank:]]Peer-Client.*\$\\d" \
		-e "\\\^.* Replace this line with your peer's fingerprint .*\$\\d" \
		-e "\\\^${fpr_tplt}\$\\d" \
		-e "\\\^</peer-fingerprint>\$\\d" "${srv_inline}" || \
			die "inline_share_fingerprint - Delete existing from SERVER"

	# Append new client
	{	"${EASYTLS_CAT}" "${srv_inline}"
		print "<peer-fingerprint>"
		"${EASYTLS_CAT}" "${srv_fp_list}"
		print "</peer-fingerprint>"
	} > "${srv_inline}-temp" || die "inline_share_fingerprint - Append new client"
	"${EASYTLS_MV}" -f "${srv_inline}-temp" "${srv_inline}"

	# Add server to inline-index
	if [ -n "${do_not_index_server_inline_file}" ]; then
		: # OK
	else
		TLSKEY_SUBNAME='NOSUBKEY'
		name="${srv_name}"
		inline_file="${srv_inline}"

		# Must unset the usage block
		unset -v inline_index_save_hash_block
		validate_hash_block="$(( validate_hash_block - 1 ))"
		if inline_index_update add; then
			: # OK
		else
			die "inline_share_fingerprint - inline_index_update add"
		fi
	fi

	# Reset inline_file to client inline
	easytls_verbose "End inline_share_fingerprint (${isfp_count})"
	inline_file="${cli_inline}"
	update_master_hash=1
} # => inline_share_fingerprint ()



############################################################################
#
# EASYTLS BUILD Section
#

# Create TLS auth file
build_tls_auth ()
{
	# Verify OpenVPN version and use correct syntax to --genkey
	verify_openvpn
	case "${openvpn_version}" in
	2.5|2.6) build_string="tls-auth" ;;
	2.4) build_string="--secret" ;;
	*) die "Unsupported OpenVPN version ${openvpn_version}"
	esac

	tlskey_file="${EASYTLS_PKI}/tls-auth.key"

	[ ! -f "${tlskey_file}" ] || \
		die "TLS auth key already exists: ${tlskey_file}"

	"${EASYTLS_OPENVPN}" --genkey "${build_string}" "${tlskey_file}" || \
		die "Failed to create TLS auth key: ${tlskey_file}"

	notice "TLS auth key created: ${tlskey_file}"
	update_master_hash=1
	easytls_verbose
} # => build_tls_auth ()

# Create TLS crypt v1 file
build_tls_crypt_v1 ()
{
	# Verify OpenVPN version and use correct syntax to --genkey
	verify_openvpn
	case "${openvpn_version}" in
	2.5|2.6) build_string="tls-crypt" ;;
	2.4) build_string="--secret" ;;
	*) die "Unsupported OpenVPN version ${openvpn_version}"
	esac

	tlskey_file="${EASYTLS_PKI}/tls-crypt.key"

	[ ! -f "${tlskey_file}" ] || \
		die "TLS crypt v1 key already exists: ${tlskey_file}"

	"${EASYTLS_OPENVPN}" --genkey "${build_string}" "${tlskey_file}" || \
		die "Failed to create TLS crypt v1 key: ${tlskey_file}"

	notice "TLS crypt v1 key created: ${tlskey_file}"
	update_master_hash=1
	easytls_verbose
} # => build_tls_crypt_v1 ()

# Create TLS crypt v2 server file
build_tls_crypt_v2_server ()
{
	[ "$#" -ge 1 ] || die "Required option(s): <server_filename_base>"

	# Verify OpenVPN version and use correct syntax to --genkey
	verify_openvpn
	case "${openvpn_version}" in
	2.5|2.6) build_string="tls-crypt-v2-server" ;;
	*) die "Unsupported OpenVPN version ${openvpn_version}"
	esac

	srv_name="${1}"

	if [ -n "${EASYTLS_NO_CA}" ]; then
		srv_cert="${EASYTLS_PKI}/${srv_name}.crt"
		tlskey_file="${EASYTLS_PKI}/${srv_name}-tls-crypt-v2.key"
	else
		srv_cert="$EASYRSA_PKI/issued/${srv_name}.crt"
		tlskey_file="$EASYTLS_PKI/${srv_name}-tls-crypt-v2.key"
	fi

	[ -f "${srv_cert}" ] || {
		help_note="Easy-TLS requires that the x509 certificate has been built."
		missing_file "${srv_cert}"
		}

	# Cert purpose
	if verify_cert_purpose "${srv_cert}" 'SSL server : Yes'; then
		cert_purpose='server'
	else
		die "Require server certificate."
	fi

	[ ! -f "${tlskey_file}" ] || die "Server file already exists: ${tlskey_file}"

	# get the serial number of the certificate from OpenSSL
	x509_cert_serial "${srv_cert}" || \
		die "build_tls_crypt_v2_server - x509_cert_serial - ${srv_cert}"

	# Use extracted_cert_serial
	cert_serial="${extracted_cert_serial}"

	"${EASYTLS_OPENVPN}" --genkey "${build_string}" "${tlskey_file}" || \
		die "Failed to create tls-crypt-v2-server key: ${tlskey_file}"

	# Save incomplete server record to key-index
	tlskey_cv2_server_serial_number "${tlskey_file}"
	tlskey_serial="${server_TLSKEY_serial_number}"
	cli_name="${srv_name}" # Improve this
	tlskey_index_update add || die "Failed to update tlskey-index"

	notice "TLS crypt v2 server key created: ${tlskey_file}"
	easytls_verbose

	if [ -n "${EASYTLS_BINLINE}" ]; then
		inline_tls_crypt_v2 "${srv_name}" || warn "Failed to build Inline file!"
	fi
} # => build_tls_crypt_v2_server ()

# Create TLS crypt v2 client file
build_tls_crypt_v2_client ()
{
	[ "$#" -ge 2 ] || \
	die "Required option(s): <server_filename_base> <client_filename_base>"

	# Verify OpenVPN version and use correct syntax to --genkey
	verify_openvpn
	case "${openvpn_version}" in
	2.5|2.6) build_string="tls-crypt-v2-client" ;;
	*) die "Unsupported OpenVPN version ${openvpn_version}"
	esac

	if [ -n "${EASYTLS_NO_CA}" ]; then
		srv_name="${1}"
		srv_cert="${EASYTLS_PKI}/${srv_name}.crt"
		ca_cert="${srv_cert}"
		shift

		cli_name="${1}"
		cli_cert="${EASYTLS_PKI}/${cli_name}.crt"
		shift
	else
		ca_cert="${EASYRSA_PKI}/ca.crt"
		srv_name="${1}"
		srv_cert="${EASYRSA_PKI}/issued/${srv_name}.crt"
		shift

		cli_name="${1}"
		cli_cert="${EASYRSA_PKI}/issued/${cli_name}.crt"
		shift
	fi

	in_file="${EASYTLS_PKI}/${srv_name}-tls-crypt-v2.key"
	tlskey_file="${EASYTLS_PKI}/${cli_name}-tls-crypt-v2.key"
	metadata_debug="${EASYTLS_META_DATA_D}/${cli_name}-tls-crypt-v2.metadata"

	[ "${TLSKEY_SUBNAME}" = 'NOSUBKEY' ] || {
		full_name="${cli_name}-${TLSKEY_SUBNAME}"
		tlskey_file="${EASYTLS_PKI}/${full_name}-tls-crypt-v2.key"
		metadata_debug="${EASYTLS_META_DATA_D}/${full_name}-tls-crypt-v2.metadata"
		}

	# don't do that ..
	[ "${srv_name}" != "${cli_name}" ] || \
		die "Server name cannot be the same as the client name."

	# Server cert
	[ -f "${srv_cert}" ] || {
		help_note="Easy-TLS requires that the X509 certificate has been built."
		missing_file "${srv_cert}"
		}

	# Cert purpose
	if verify_cert_purpose "${srv_cert}" 'SSL server : Yes'; then
		: # Not used: srv_cert_purpose='server'
	else
		die "Require server certificate."
	fi

	# Client cert
	[ -f "${cli_cert}" ] || {
		help_note="Easy-TLS requires that the X509 certificate has been built."
		missing_file "${cli_cert}"
		}

	# Cert purpose
	if verify_cert_purpose "${cli_cert}" 'SSL client : Yes'; then
		cert_purpose='client'
	else
		die "Require client certificate."
	fi

	# Server tlskey MUST exist
	[ -f "${in_file}" ] || {
		help_note="You must first build a TLS-Crypt-V2 Server key."
		die "Server key does not exist: ${in_file}"
		}

	# Client tlskey MUST NOT exist
	[ ! -f "${tlskey_file}" ] || {
		help_note="To remove a TLS-key file use: easytls remove"
		die "Client key already exists: ${tlskey_file}"
		}

	# Capture HW and IP addresses
	EASYTLS_TLSCV2_HWLIST=""
	while [ -n "${1}" ]; do
		# Needs to allow multiples. EG: eth & wifi
		md_temp_addr="${1}"
		if hw_addr_hex_check "${md_temp_addr}"; then
			EASYTLS_TLSCV2_HWLIST="${EASYTLS_TLSCV2_HWLIST} ${temp_hw_addr}"

		elif validate_ip4_data "${md_temp_addr}"; then
			md_temp_addr="${valid_octets}/${mask_len}"
			if expand_ip4_address "${md_temp_addr}"; then
				# Returned: ${full_valid_hextets} ${full_subnet_addr6}"
				: # OK
				#md_temp_addr="${full_subnet_addr6}"
			else
				error_msg "This is not a valid 'subnet/mask' for Easy-TLS."
				help_note="To validate, use: ./easytls x4ip ${md_temp_addr}"
				die "Invalid subnet/mask"
			fi
			unset -v valid_octets mask_len
			EASYTLS_TLSCV2_HWLIST="${EASYTLS_TLSCV2_HWLIST} ${md_temp_addr}"

		elif validate_ip6_data "${md_temp_addr}"; then
			md_temp_addr="${valid_hextets}/${mask_len}"
			if expand_ip6_address "${md_temp_addr}"; then
				# Returned: ${full_valid_hextets} ${full_subnet_addr6}"
				: # OK
				md_temp_addr="${full_subnet_addr6}"
			else
				error_msg "This is not a valid 'subnet/mask' for Easy-TLS."
				help_note="To validate, use: ./easytls x6ip ${md_temp_addr}"
				die "Invalid subnet/mask"
			fi
			unset -v valid_octets mask_len
			EASYTLS_TLSCV2_HWLIST="${EASYTLS_TLSCV2_HWLIST} ${md_temp_addr}"
		else
			die "Invalid Address: ${md_temp_addr}"
		fi
		shift
	done
	export EASYTLS_TLSCV2_HWLIST

	# Confirm metadata Custom-Group
	[ "${TLSKEY_CUSTOM_GRP}" = "EASYTLS" ] || {
		confirm "Is the Custom-Group field correct ? " "yes" \
		"Custom-Group field for metadata: ${TLSKEY_CUSTOM_GRP}"
		}

	# Confirm --sub-key-name)
	[ "${TLSKEY_SUBNAME}" = "NOSUBKEY" ] || {
		confirm "Is the Sub-key-name correct ? " "yes" \
		"Sub-key-name field for metadata: ${TLSKEY_SUBNAME}"
		}

	# Confirm metadata Hardware-Address(es)
	if [ -n "${EASYTLS_TLSCV2_HWLIST}" ]; then
		[ -z "${EASYTLS_BATCH}" ] || \
			print "* All Hardware-Addresses are valid length hexidecimal values."
		confirm "Is the Hardware-Address field correct ? " "yes" \
			"Hardware-Address field for metadata: ${EASYTLS_TLSCV2_HWLIST}"
	fi

	metadata=""
	b64_metadata=""
	b64_enc_metadata || die "Failed to base64 encode metadata"

	# Build key
	"${EASYTLS_OPENVPN}" --tls-crypt-v2 "${in_file}" \
		--genkey "${build_string}" "${tlskey_file}" "${b64_metadata}" || \
			die "Failed to create tls-crypt-v2-client key: ${tlskey_file}"

	# Save serial number to key-index
	#tlskey_serial=${tlskey_serial}
	tlskey_index_update add || die "Failed to update tlskey-index"

	notice "TLS crypt v2 client key created: ${tlskey_file}"
	easytls_verbose

	if [ -n "${EASYTLS_BINLINE}" ]; then
		inline_tls_crypt_v2 "${cli_name}" || warn "Failed to build Inline file!"
	fi
} # => build_tls_crypt_v2_client ()

# Build group TLS-Crypt-v2 Server
build_tls_cv2_group_server ()
{
	#print "Only Client keys require the Group setting."
	#print "Use your current standard Server key to build"
	#print "new Client Group key(s) with."
	#return 1

	[ "$#" -ge 1 ] || die "Required option(s): <server_group_name>"

	# Verify OpenVPN version and use correct syntax to --genkey
	verify_openvpn
	case "${openvpn_version}" in
	2.5|2.6) build_string="tls-crypt-v2-server" ;;
	*) die "Unsupported OpenVPN version ${openvpn_version}"
	esac

	#grp_name="${1}-grp-srv"
	grp_name="${1}"
	tlskey_file="${EASYTLS_PKI}/${grp_name}-tls-crypt-v2.key"

	[ ! -f "${tlskey_file}" ] || \
		die "Group Server key already exists: ${tlskey_file}"

	"${EASYTLS_OPENVPN}" --genkey "${build_string}" "${tlskey_file}" || \
		die "Failed to create tls-crypt-v2-server key: ${tlskey_file}"

	# Save incomplete server record to key-index
	group_mode=1
	tlskey_cv2_server_serial_number "${tlskey_file}"
	tlskey_serial="${server_TLSKEY_serial_number}"
	cert_serial="00000000000000000000000000000000"
	cli_name="${grp_name}" # Improve this
	tlskey_index_update add || die "Failed to update tlskey-index"

	notice "TLS-Crypt-V2 GROUP Server Key created: ${tlskey_file}"
	easytls_verbose
} # => build_tls_cv2_group_server ()

# Build group TLS-Crypt-v2 Client
build_tls_cv2_group_client ()
{
	[ "$#" -ge 2 ] || \
		die "Required option(s): <server_group_name> <client_group_name>"

	# Verify OpenVPN version and use correct syntax to --genkey
	verify_openvpn
	case "${openvpn_version}" in
	2.5|2.6) build_string="tls-crypt-v2-client" ;;
	*) die "Unsupported OpenVPN version ${openvpn_version}"
	esac

	srv_key_name="${1}"; shift
	cli_grp_name="${1}"; shift

	#in_file="${EASYTLS_PKI}/${srv_grp_name}-tls-crypt-v2.key"
	in_file="${EASYTLS_PKI}/${srv_key_name}-tls-crypt-v2.key"
	tlskey_file="${EASYTLS_PKI}/${cli_grp_name}-tls-crypt-v2.key"
	metadata_debug="${EASYTLS_META_DATA_D}/${cli_grp_name}-tls-crypt-v2.metadata"

	# Server tlskey MUST exist
	[ -f "${in_file}" ] || {
		help_note="You must first build a TLS-Crypt-V2 Server key."
		die "Server key does not exist: ${in_file}"
		}

	# Client tlskey MUST NOT exist
	[ ! -f "${tlskey_file}" ] || {
		help_note="To remove a TLS-key file use: easytls remove"
		die "Client key already exists: ${tlskey_file}"
		}

	# Capture HW and IP addresses
	EASYTLS_TLSCV2_HWLIST=
	while [ -n "${1}" ]; do
		# Needs to allow multiples. EG: eth & wifi
		md_temp_addr="${1}"
		if hw_addr_hex_check "${md_temp_addr}"; then
			EASYTLS_TLSCV2_HWLIST="${EASYTLS_TLSCV2_HWLIST} ${temp_hw_addr}"
		elif validate_ip4_data "${md_temp_addr}"; then
			md_temp_addr="${valid_octets}/${mask_len}"
			if expand_ip4_address "${md_temp_addr}"; then
				# Returned: ${full_valid_hextets} ${full_subnet_addr6}"
				: # OK
				#md_temp_addr="${full_subnet_addr6}"
			else
				error_msg "This is not a valid 'subnet/mask' for Easy-TLS."
				help_note="To validate, use: ./easytls x4ip ${md_temp_addr}"
				die "Invalid subnet/mask"
			fi
			unset -v valid_octets mask_len
			EASYTLS_TLSCV2_HWLIST="${EASYTLS_TLSCV2_HWLIST} ${md_temp_addr}"
		elif validate_ip6_data "${md_temp_addr}"; then
			md_temp_addr="${valid_hextets}/${mask_len}"
			if expand_ip6_address "${md_temp_addr}"; then
				# Returned: ${full_valid_hextets} ${full_subnet_addr6}"
				: # OK
				md_temp_addr="${full_subnet_addr6}"
			else
				error_msg "This is not a valid 'subnet/mask' for Easy-TLS."
				help_note="To validate, use: ./easytls x6ip ${md_temp_addr}"
				die "Invalid subnet/mask"
			fi
			unset -v valid_octets mask_len
			EASYTLS_TLSCV2_HWLIST="${EASYTLS_TLSCV2_HWLIST} ${md_temp_addr}"
		else
			die "Invalid Address: ${md_temp_addr}"
		fi
		shift
	done
	export EASYTLS_TLSCV2_HWLIST

	# Confirm metadata Custom-Group
	[ "${TLSKEY_CUSTOM_GRP}" = "EASYTLS" ] || {
		confirm "Is the Custom-Group field correct ? " "yes" \
		"Custom-Group field for metadata: ${TLSKEY_CUSTOM_GRP}"
		}

	# Confirm metadata Hardware-Address(es)
	if [ -n "${EASYTLS_TLSCV2_HWLIST}" ]; then
		[ -z "${EASYTLS_BATCH}" ] || \
			print "All Address filters are valid values."
		confirm "Are the Address filters correct ? " "yes" \
			"Address filters for metadata: ${EASYTLS_TLSCV2_HWLIST}"
	fi

	group_mode=1
	metadata=""
	b64_metadata=""
	b64_enc_metadata || die "Failed to base64 encode metadata"

	# Build key
	"${EASYTLS_OPENVPN}" --tls-crypt-v2 "${in_file}" \
		--genkey "${build_string}" "${tlskey_file}" "${b64_metadata}" || \
			die "Failed to create tls-crypt-v2-client key: ${tlskey_file}"

	# Save serial number to key-index
	tlskey_index_update add || die "Failed to update tlskey-index"

	notice "TLS-Crypt-V2 GROUP Client Key created: ${tlskey_file}"
	easytls_verbose
	update_master_hash=1

	if [ -n "${EASYTLS_BINLINE}" ];	then
		inline_tls_crypt_v2 "${cli_name}" || warn "Failed to build Inline file!"
	fi
} # => build_tls_cv2_group_client ()

# Verify the input is a 12 digit hex value and export it to the HW list
hw_addr_hex_check ()
{
	temp_hw_addr="$("${EASYTLS_PRINTF}" '%s' "${1}" | \
		"${EASYTLS_SED}" -e 's/\://g' -e 's/\-//g' -e 'y/abcdef/ABCDEF/')"

	[ "${#temp_hw_addr}" -eq 12 ] || {
		#easytls_verbose "Hardware Address must be 12 digits exactly!"
		return 2
		}

	# Verify hex only
	[ "${temp_hw_addr}" = "${temp_hw_addr%[!0123456789ABCDEF]*}" ] || {
		#easytls_verbose "temp_hw_addr - !Hex: ${temp_hw_addr}"
		return 3
		}
} # => hw_addr_hex_check ()

# Base64 encode metadata fields
b64_enc_metadata ()
{
	# metadata strings:
	#
	# All fields are single contiguous words
	#
	# 1.  "TLS_Key_Serial_Number"-"Random padding"--'easytls'-"$EASYTLS_VERSION"
	#     "Random padding" and "$EASYTLS_VERSION" are non-functional
	#
	# 2   CA Identity - CA fingerprint
	#     `OpenSSL output`; Drop decription and remove colons ':' (Hex only field)
	#
	# 2.1 Server Fingerprint - Formatted as a CA Identity
	#     In No-CA mode the server fingerprint is used instead of the CA
	#
	# 2.2 Server name
	#
	#
	# 3.  Client certificate serial
	#     `OpenSSL output`; Drop the 'serial=' (Hex only field)
	#
	# 4.  Creation Date of this key:
	#     %s     seconds since 1970-01-01 00:00:00 UTC
	#
	# 5.  Custom Group string
	#     Default: EASYTLS
	#
	# 6.  Client CommonName
	#
	# 7.  Sub-key Name
	#     Default: NOSUBKEY
	#
	# 8.  Option - TBD
	#     Default: OPT
	#
	# 9.  Hardware Address string:
	#     Default: =000000000000=
	#

	# metadata_version
	metadata_version="easytls-${EASYTLS_VERSION}"

	# Set creation Date
	key_date="${local_time_unix}"

	if [ -n "${group_mode}" ]; then

		# Group mode client key MUST know the server from which it was built
		srv_name="${srv_key_name}"

		# In Group mode easytls abandons x509 CA
		# A TLS-CV2 Group Server and Client Key cannot be aligned to any x509
		ca_identity="${group_mode_fixed_ca_id}"

		# Append TLS-CV2 Group Server name to CA-ID
		ca_identity="${ca_identity}-${srv_name}"

		# Group mode - No client x509
		cert_serial="00000000000000000000000000000000"

		# Build metadata
		metadata="${metadata_version}${delimiter}${ca_identity}"
		metadata="${metadata}${delimiter}${cert_serial}${delimiter}${key_date}"

		# Append --custom-group
		metadata="${metadata}${delimiter}${TLSKEY_CUSTOM_GRP}"

		# Append common_name
		cli_name="${cli_grp_name}"
		metadata="${metadata}${delimiter}${cli_name}"

		# Append --sub-key-name
		metadata="${metadata}${delimiter}${TLSKEY_SUBNAME}"

	else

		# CA Identity
		generate_ca_identity || {
			error_msg "b64_enc_metadata - generate_ca_identity"
			return 1
			}

		# Append Server name to CA-ID
		ca_identity="${ca_identity}-${srv_name}"

		# get the serial number of the certificate from OpenSSL
		x509_cert_serial "${cli_cert}" || {
			error_msg "b64_enc_metadata - x509_cert_serial - ${cli_cert}"
			return 1
			}

		# Use extracted_cert_serial
		cert_serial="${extracted_cert_serial}"

		# Build metadata
		metadata="${metadata_version}${delimiter}${ca_identity}"
		metadata="${metadata}${delimiter}${cert_serial}${delimiter}${key_date}"

		# Append --custom-group
		metadata="${metadata}${delimiter}${TLSKEY_CUSTOM_GRP}"

		# Append common_name
		[ -n "${cli_name}" ] || {
			error_msg "no client name"
			return 1
			}
		metadata="${metadata}${delimiter}${cli_name}"

		# Append --sub-key-name
		metadata="${metadata}${delimiter}${TLSKEY_SUBNAME}"
	fi

	# Append opt field (Usage is to be decided)
	if [ -n "${EASYTLS_TLSCV2_OPT}" ]; then
		metadata="${metadata}${delimiter}${EASYTLS_TLSCV2_OPT}"
	else
		metadata="${metadata}${delimiter}OPT"
	fi

	# Append --hw-addr
	if [ -n "${EASYTLS_TLSCV2_HWLIST}" ]; then
		MD_HW_LIST=''
		for i in ${EASYTLS_TLSCV2_HWLIST}; do
			MD_HW_LIST="${MD_HW_LIST}=${i}"
		done
		metadata="${metadata}${delimiter}${MD_HW_LIST}="
	else
		metadata="${metadata}${delimiter}=000000000000="
	fi

	# TLS Crypt v2 client key serial number
	tlskey_cv2_client_serial_number || {
		error_msg "b64_enc_metadata - tlskey_cv2_client_serial_number"
		return 1
		}

	# Save metadata in plain text
	"${EASYTLS_PRINTF}" "%s\n" "${metadata}" > "${metadata_debug}" || {
		error_msg "b64_enc_metadata - Save metadata_debug"
		return 1
		}

	# List metadata
	easytls_verbose "metadata:
${metadata}"

	# Base64 encode metadata
	easytls_ssl_encode_base64_data "${metadata}" || {
		error_msg "b64_enc_metadata - easytls_ssl_encode_base64_data"
		return 1
		}
	# Use generated b64 metadata
	b64_metadata="${generated_base64}"
	unset -v generated_base64
} # => b64_enc_metadata ()

# Build self-signed server cert and key
build_self_sign ()
{
	[ "$#" -ge 2 ] || die "Required option(s): <Name>"

	cert_type="${1}"
	shift # Scrape off type

	cert_name="${1}"
	shift # Scrape off name

	cert_file="${EASYTLS_PKI}/${cert_name}.crt"
	key_file="${EASYTLS_PKI}/${cert_name}.key"
	fpr_file="${EASYTLS_PKI}/${cert_name}.fpr"
	inline_file="${EASYTLS_PKI}/${cert_name}.inline"

	[ ! -f "${cert_file}" ] || {
		help_note="Not over-writing current certificate"
		die "Certificate exists: ${cert_file}"
		}

	[ ! -f "${key_file}" ] || {
		help_note="Not over-writing current key"
		die "Key exists: ${key_file}"
		}

	[ ! -f "${inline_file}" ] || {
		help_note="Not over-writing current inline file"
		die "Inline file exists: ${inline_file}"
		}

	# Certificate type
	case "${cert_type}" in
	[sS]erver)
		# Certificate extension: TLS Web Server
		EASYTLS_EKU="serverAuth"
		cert_purpose='Server'
	;;
	[cC]lient)
		# Certificate extension: TLS Web Server
		EASYTLS_EKU="clientAuth"
		cert_purpose='Client'
	;;
	*)
		die "Unknown certificate type: ${cert_type}"
	esac

	# Verify Major version of openssl
	verify_openssl || die "build_self_sign - verify_openssl"

	# Disable password
	if [ -z "${EASYTLS_SS_PASSWORD}" ]; then
		case "${openssl_version}" in
		3)	openssl_nopass="-noenc" ;;
		1)	openssl_nopass="-nodes" ;;
		*)	die "Unsupport openssl version: ${openssl_version}"
		esac
	fi

	# build
	create_self_sign_cert_pair || \
		die "Failed to create self-signed ${cert_type} cert/key for ${cert_name}"

	# Generate FP
	easytls_ssl_generate_fingerprint "${cert_file}" || {
		error_msg "build_self_sign - easytls_ssl_generate_fingerprint"
		return 1
		}
	cert_fpr="${certificate_fingerPrint}"
	unset -v certificate_fingerPrint

	# Write FP file - Always over-write any current file
	"${EASYTLS_PRINTF}" "%s\n" "${cert_fpr}" >  "${fpr_file}" || {
		error_msg "build_self_sign - Write FP file"
		return 1
		}

	# Create base-inline-file - Clients cannot generate TLS-keys
	# Do not add this inline-file to inline-index
	{
		"${EASYTLS_PRINTF}" '%s\n' \
			"# Easy-TLS self-signed certificate base-inline file"
		"${EASYTLS_PRINTF}" '%s\n' "# ${cert_type}: ${cert_name}"
		"${EASYTLS_PRINTF}" '%s\n\n' \
			"# Send this to your peer - Fingerprint: ${cert_fpr}"
		"${EASYTLS_PRINTF}" '%s\n' "<cert>"
		"${EASYTLS_CAT}" "${cert_file}"
		"${EASYTLS_PRINTF}" '%s\n\n' "<\cert>"

		# Add key
		"${EASYTLS_PRINTF}" '%s\n' "<key>"
		"${EASYTLS_CAT}" "${key_file}"
		"${EASYTLS_PRINTF}" '%s\n\n' "<\key>"

		# Share fingerprint template
		inline_share_fingerprint || \
			die "build_self_sign - inline_share_fingerprint"
	} > "${inline_file}" || die "Failed to create Inline file for ${cert_name}"

	# share this client FP with server defined by -r=<serv-name> option
	inline_share_fingerprint "${cert_name}" || \
		die "inline_share_fingerprint - build_self_sign"

	notice "Self signed ${cert_type} certificate/key created for ${cert_name}"
	notice " * Certificate: ${cert_file}"
	notice " *         Key: ${key_file}"
	notice " * Fingerprint: ${fpr_file}"
	notice " * Inline file: ${inline_file}"
	if "${EASYTLS_GREP}" -q "<tls-auth>|<tls-crypt.*>" "${inline_file}"; then
		:
	else
		print "   This inline-file requires a TLS-key"
	fi
	if "${EASYTLS_GREP}" -q \
		"^.* Replace this line with your peer's fingerprint .*$" \
		"${inline_file}"
	then
		print "   AND peer-fingerprint"
	fi
	easytls_verbose
} # => build_self_sign_server ()

# Create an EC key/cert pair
create_self_sign_cert_pair ()
{
	# Setup easytls-openssl.cnf and env-var OPENSSL_CNF
	easytls_ssl_file create || \
		die "Failed to create easytls-openssl.cnf (Permission)"

	# If ecparams fail then so does req
	easytls_verbose
	"${EASYRSA_OPENSSL}" ecparam -name "${EASYTLS_EC_CURVE}" \
		> "${EASYTLS_ECPARAM_TMP}"
	# Separate commands for utterly stupid -nodes/-noenc not set error
	if [ -z "${EASYTLS_SS_PASSWORD}" ]; then
		"${EASYRSA_OPENSSL}" req -x509 -newkey ec:"${EASYTLS_ECPARAM_TMP}" \
			-sha256 -days="${EASYTLS_SS_AGE}" -text -utf8 \
			"${openssl_nopass}" \
			-keyout "${key_file}" -out "${cert_file}" -subj "/CN=${cert_name}" \
			-addext extendedKeyUsage="${EASYTLS_EKU}" || return 1
	else
		"${EASYRSA_OPENSSL}" req -x509 -newkey ec:"${EASYTLS_ECPARAM_TMP}" \
			-sha256 -days="${EASYTLS_SS_AGE}" -text -utf8 \
			-keyout "${key_file}" -out "${cert_file}" -subj "/CN=${cert_name}" \
			-addext extendedKeyUsage="${EASYTLS_EKU}" || return 1
	fi

	# Remove easytls-openssl.cnf and env-var OPENSSL_CNF
	easytls_ssl_file destroy || \
		die "Failed to destroy easytls-openssl.cnf (Permission)"
	update_master_hash=1
	"${EASYTLS_RM}" -f "${EASYTLS_ECPARAM_TMP}"
} # => create_self_sign_cert_pair ()



#=# 9273398a-5284-4c1f-aec5-d597ceb1d085

# Verbose message
verbose_easytls_tctip_lib ()
{
	[ -z "${EASYTLS_SILENT}" ] || return 0
	[ -n "${EASYTLS_TCTIP_LIB_VERBOSE}" ] || return 0
	"${EASYTLS_PRINTF}" '%s\n' "${1}"
} # => verbose_easytls_tctip_lib ()

# Front end validate IP address
validate_ip_address ()
{
	[ "${1}" = "${1%%.*}" ] || ipv4=1
	[ "${1}" = "${1%%:*}" ] || ipv6=1
	[ -n "${ipv4}${ipv6}" ] || return 1
	if [ -n "${ipv4}" ] && [ -n "${ipv6}" ]; then
		easytls_verbose "Unsupported <:Port>"
		return 1
	fi
	[ -n "${ipv4}" ] && validate_ip4_data "$@" && valid4=1
	[ -n "${ipv6}" ] && validate_ip6_data "$@" && valid6=1
	[ -n "${valid4}" ] && [ -n "${valid6}" ] && return 1
	[ -z "${valid4}" ] && [ -z "${valid6}" ] && return 1
} # => validate_ip_address ()

# Exit with error
invalid_address ()
{
	case "${1}" in
	1) print "easytls invalid" ;;
	10) print "excess input" ;;
	11) print "illegal format" ;;
	12) print "illegal mask" ;;
	13) print "mask range" ;;
	14) print "leading zero" ;;
	15) print "class range" ;;
	16) print "class count" ;;
	17) print "cidr mask" ;;
	18) print "input error" ;;
	19) print "ip2dec error" ;;
	20)
		print "ip6/mask-length mismatch"
		print "ip6/mask correct example: 1:22:333:4444:5::/80"
	;;
	*) print "undocumented ${1} ?" ;;
	esac
} # => invalid_address ()

# IPv4 address to decimal
ip2dec ()
{
	case "${1}" in
	*[!1234567890.]* | .* | *. | *..* ) return 1 ;;
	*.*.*.* ) : ;; #OK
	* ) return 1 ;;
	esac

	temp_ip_addr="${1}"
	a="${temp_ip_addr%%.*}"; temp_ip_addr="${temp_ip_addr#*.}"
	b="${temp_ip_addr%%.*}"; temp_ip_addr="${temp_ip_addr#*.}"
	c="${temp_ip_addr%%.*}"; temp_ip_addr="${temp_ip_addr#*.}"
	d="${temp_ip_addr%%.*}"

	for i in "${a}" "${b}" "${c}" "${d}"; do
		[ "${#i}" -gt 1 ] || continue
		[ -n "${i%%0*}" ] || return 1
		if [ 0 -gt "${i}" ] || [ "${i}" -gt 255 ]; then return 1; fi
	done
	ip4_dec="$(( (a << 24) + (b << 16) + (c << 8) + d ))" || return 1
	unset -v temp_ip_addr a b c d
} # => ip2dec ()

# IPv4 CIDR mask length to decimal
cidrmask2dec ()
{
	mask_dec=0
	imsk_dec=0
	count=32 # or 128 - If possible..
	power=1
	while [ "${count}" -gt 0 ]; do
		count="$(( count - 1 ))"
		if [ "${1}" -gt "${count}" ]; then
			# mask
			mask_dec="$(( mask_dec + power ))"
		else
			# inverse
			imsk_dec="$(( imsk_dec + power ))"
		fi
		power="$(( power * 2 ))"
	done
	unset -v count power
} # => cidrmask2dec ()

# EXPAND IPv6
expand_ip6_address ()
{
	[ -z "${2}" ] || return 10
	in_ip_addr="${1}"
	shift

	in_valid_hextets="${in_ip_addr%/*}"
	in_valid_mask_len="${in_ip_addr##*/}"
	unset -v in_ip_addr

	# mask length
	case "${in_valid_mask_len}" in
	"${in_valid_hextets}" | '' ) in_valid_mask_len=128 ;;
	[!1234567890] | 0* ) return 11 ;;
	* ) : # OK
	esac
	if [ 0 -gt "${in_valid_mask_len}" ] || [ "${in_valid_mask_len}" -gt 128 ]
	then
		return 11
	fi

	# ADDRESS 6
	temp_valid_hextets="${in_valid_hextets}"

	# expand leading colon
	[ "${temp_valid_hextets}" = "${temp_valid_hextets#:}" ] || \
		lead_colon=1
	[ -z "${lead_colon}" ] || temp_valid_hextets="0${temp_valid_hextets}"

	# Count valid compressed hextets
	count_valid_hextets=0
	while [ -n "${temp_valid_hextets}" ]; do
		count_valid_hextets="$(( count_valid_hextets + 1 ))"
		if [ "${temp_valid_hextets}" = "${temp_valid_hextets#*:}" ]; then
			temp_valid_hextets="${temp_valid_hextets}:"
		fi
		temp_valid_hextets="${temp_valid_hextets#*:}"
		temp_valid_hextets="${temp_valid_hextets#:}"
	done
	verbose_easytls_tctip_lib "count_valid_hextets: ${count_valid_hextets}"

	# expand double colon
	temp_valid_hextets="${in_valid_hextets}"
	expa_valid_hextets="${in_valid_hextets}"
	if [ "${count_valid_hextets}" -lt 8 ]; then
		hi_part="${temp_valid_hextets%::*}"
		lo_part="${temp_valid_hextets#*::}"
		missing_zeros="$(( 8 - count_valid_hextets ))"
		while [ "${missing_zeros}" -gt 0 ]; do
			hi_part="${hi_part}:0"
			missing_zeros="$(( missing_zeros - 1 ))"
		done
		unset -v missing_zeros
		expa_valid_hextets="${hi_part}:${lo_part}"
		# Re-expand leading colon
		[ -z "${lead_colon}" ] || expa_valid_hextets="0${expa_valid_hextets}"
	fi
	# Save the orangutan
	unset -v lead_colon lo_part hi_part count_valid_hextets
	verbose_easytls_tctip_lib "expa_valid_hextets: ${expa_valid_hextets}"

	temp_valid_hextets="${expa_valid_hextets}"
	hex_count=8
	unset -v full_valid_hextets delim
	# Expand compressed zeros
	while [ "${hex_count}" -gt 0 ]; do
		hextet="${temp_valid_hextets%%:*}"
		while [ "${#hextet}" -lt 4 ]; do
			hextet="0${hextet}"
		done
		full_valid_hextets="${full_valid_hextets}${delim}${hextet}"
		delim=':'
		temp_valid_hextets="${temp_valid_hextets#*:}"
		hex_count="$(( hex_count - 1 ))"
	done
	# Save "The violence inherent in the system"
	unset -v hex_count delim
	verbose_easytls_tctip_lib "full_valid_hextets: ${full_valid_hextets}"

	# Split IP at mask_len
	[ "$(( in_valid_mask_len % 4 ))" -eq 0 ] || \
		die "in_valid_mask_len % 4: ${in_valid_mask_len}"
	hex_mask="$(( in_valid_mask_len / 4 ))"

	temp_valid_hextets="${full_valid_hextets}"
	while [ "${hex_mask}" -gt 0 ]; do
		delete_mask="${temp_valid_hextets#?}"
		verbose_easytls_tctip_lib "delete_mask: ${delete_mask}"
		hex_char="${temp_valid_hextets%"${delete_mask}"}"
		verbose_easytls_tctip_lib "hex_char: ${hex_char}"
		temp_valid_hextets="${temp_valid_hextets#?}"
		verbose_easytls_tctip_lib "temp_valid_hextets: ${temp_valid_hextets}"
		full_subnet_addr6="${full_subnet_addr6}${hex_char}"
		verbose_easytls_tctip_lib "full_subnet_addr6: ${full_subnet_addr6}"
		[ "${hex_char}" = ':' ] || hex_mask="$(( hex_mask - 1 ))"
		verbose_easytls_tctip_lib "*** hex_mask: ${hex_mask}"
	done
	# Save the polar ice-caps
	unset -v hex_char hex_mask delete_mask

	# The remainder should equal zero
	while [ -n "${temp_valid_hextets}" ]; do
		hextet="${temp_valid_hextets%%:*}"
		if [ -z "${hextet}" ]; then
			temp_valid_hextets="${temp_valid_hextets#*:}"
			hextet="${temp_valid_hextets%%:*}"
		fi

		if [ "${temp_valid_hextets}" = "${temp_valid_hextets#*:}" ]; then
			temp_valid_hextets="${temp_valid_hextets}:"
		fi
		temp_valid_hextets="${temp_valid_hextets#*:}"

		# shellcheck disable=SC2249 # (info): default *) case
		case "${hextet}" in
		*[!0:]* ) return 20 ;;
		esac
	done
	verbose_easytls_tctip_lib "full_valid_hextets: ${full_valid_hextets}"
	verbose_easytls_tctip_lib "full_subnet_addr6: ${full_subnet_addr6}"
	verbose_easytls_tctip_lib "temp_valid_hextets: ${temp_valid_hextets}"
	# Save the trees
	unset -v hextet temp_valid_hextets
	# Return full_valid_hextets full_subnet_addr6
} # => expand_ip6_address ()

#=# b66633f8-3746-436a-901f-29638199b187

# EXPAND IPv4
# This tests that the subnet/mask are "equivalent"
expand_ip4_address ()
{
	validate_ip4_data "$@" || die "$* - validate_ip4_data - expand_ip4_address"
	# Verify IP matches mask (eg: 1.2.3.0/24 ok, 1.2.3.4/24 bad)
	temp_a4andm_dec="$(( temp_ip4_addr_dec & temp_ip4_mask_dec ))"
	[ "${temp_a4andm_dec}" -eq "${temp_ip4_addr_dec}" ] && return 0
} # => expand_ip4_address ()

# Validate IPv4 data
validate_ip4_data ()
{
	[ -z "${2}" ] || return 10
	temp_ip_addr="${1}"

	# Syntax
	case "${temp_ip_addr}" in
	*[!0123456789./]* | .* | *. | *..* | */*.* ) return 11 ;;
	*.*.*.* ) : ;; #OK
	* ) return 1 ;;
	esac

	# Netmask
	mask_len="${temp_ip_addr##*/}"
	if [ "${mask_len}" = "${temp_ip_addr}" ]; then
		mask_len=32
	else
		temp_ip_addr="${temp_ip_addr%/*}"
		[ -n "${mask_len}" ] || return 12
		[ -n "${mask_len%%0*}" ] || return 12
		if [ "${mask_len}" -lt 0 ] || [ "${mask_len}" -gt 32 ]; then
			return 13
		fi
	fi

	# Valid mask to decimal
	cidrmask2dec "${mask_len}" || return 17
	temp_ip4_mask_dec="${mask_dec}"
	#key_ip4_imsk_dec="${imsk_dec}"
	unset -v mask_dec imsk_dec

	# Address
	unset -v valid_octets delim
	i=0
	while [ -n "${temp_ip_addr}" ]; do
		i=$(( i + 1 ))
		octet="${temp_ip_addr%%.*}"

		if [ "${octet}" != "${octet#0}" ]; then
			[ "${octet}" = "0" ] || return 14
		fi

		if [ "${octet}" -lt 0 ] || [ "${octet}" -gt 255 ]; then
			return 15
		fi

		valid_octets="${valid_octets}${delim}${octet}"
		delim='.'

		# Break after last octet
		[ "${temp_ip_addr}" != "${temp_ip_addr#*.}" ] || break

		# Drop the left most "$octet."
		temp_ip_addr="${temp_ip_addr#*.}"
	done
	# *.*.*.* four octets?
	[ "${i}" -eq 4 ] || return 16
	unset -v temp_ip_addr delim octet i

	# Valid IPv4 to decimal
	ip2dec "${valid_octets}" || return 19
	temp_ip4_addr_dec="${ip4_dec}"
	unset -v ip4_dec
	# Return: temp_ip4_addr_dec ; temp_ip4_mask_dec ; valid_octets ; mask_len
} # => validate_ip4_data ()

# Validate IPv6 data
validate_ip6_data ()
{
	[ -z "${2}" ] || return 10
	temp_ip_addr="${1}"

	# Syntax
	case "${temp_ip_addr}" in
	#:[!:]* ) return 11 ;;
	*[!:]: ) return 11 ;;
	*[!:]:/* ) return 11 ;;
	*::*::* ) return 11 ;;
	*/*:* ) return 11 ;;
	*[!0123456789abcdef:/]* ) return 11 ;;
	*) : # OK
	esac

	# Netmask
	unset -v valid_mask_len
	mask_len="${temp_ip_addr##*/}"
	if [ "${mask_len}" = "${temp_ip_addr}" ]; then
		mask_len=128
	else
		temp_ip_addr="${temp_ip_addr%/*}"
	fi

	[ -n "${mask_len}" ] || return 12
	# shellcheck disable=SC2249 # (info): default *) case
	case "${mask_len}" in
	*[!0123456789]* | 0* ) return 11 ;;
	esac
	if [ "${mask_len}" -lt 0 ] || [ "${mask_len}" -gt 128 ]; then
		return 13
	fi
	valid_mask_len="${mask_len}"

	# Address
	unset -v valid_hextets delim
	i=0
	while [ -n "${temp_ip_addr}" ]; do
		i="$(( i + 1 ))"
		unset -v hextet

		# Leading : to current string
		if [ -z "${temp_ip_addr%%:*}" ]; then
			if [ "${i}" -eq 1 ]; then
				# Leading single :
				# Does not count as double_colon
				[ -z "${lead_colon}" ] || return 19
				lead_colon=1
				hextet=":"
			else
				# right-hand colon in '::'
				# The left-hand colon was stripped with the last hextet
				[ -z "${double_colon}" ] || return 17
				double_colon=1
				hextet=":"
				unset -v delim
			fi
		fi

		# Left to right
		temptet="${temp_ip_addr%%:*}"
		hextet="${hextet:-${temptet}}"
		unset -v temptet

		if [ "${hextet}" = ":" ]; then
			# OK
			:
		else
			# Range: 0 < hextet < 65535
			if [ 0 -gt "$(( 0x${hextet} ))" ] || \
				[ "$(( 0x${hextet} ))" -gt 65535 ]
			then
				return 15
			fi
		fi

		if [ -n "${lead_colon}" ] && [ "${i}" -eq 1 ]; then	unset -v hextet; fi
		valid_hextets="${valid_hextets}${delim}${hextet}"
		delim=':'

		# Break after last hextet
		[ "${temp_ip_addr}" != "${temp_ip_addr#*:}" ] || break

		# Drop the left most 'ffff:' not '::'
		temp_ip_addr="${temp_ip_addr#*:}"
	done

	# shudder
	if [ -n "${double_colon}" ]; then
		{ [ "${i}" -gt 1 ] && [ "${i}" -lt 9 ]; } || return 16
	else
		[ "${i}" -eq 8 ] || return 16
	fi
	# Save the atmosphere
	unset -v temp_ip_addr delim hextet i double_colon lead_colon

	# "$full_valid_ip6_addr" is not use in easytls-tctip.lib
	# shellcheck disable=2034
	full_valid_ip6_addr="${valid_hextets}/${valid_mask_len}"
	# Return: valid_hextets ; valid_mask_len ; full_valid_ip6_addr
} # => validate_ip6_data ()

#=# 7f97f537-eafd-40c3-8f31-2fee10c12ad3



# easytls-metadata.lib
#=# 35579017-b084-4d6b-94d5-76397c2d4a1f

# Break metadata_string into variables
# shellcheck disable=SC2034 # foo appears unused. Verify it or export it.
metadata_string_to_vars ()
{
	MD_TLSKEY_SERIAL="${1%%-*}" || return 1

	#seed="${*}" || return 1
	#MD_SEED="${seed#*-}" || return 1
	#unset -v seed

	#md_padding="${md_seed%%--*}" || return 1
	md_easytls_ver="${1#*--}" || return 1
	MD_EASYTLS="${md_easytls_ver%-*}" || return 1
	unset -v md_easytls_ver

	MD_IDENTITY="${2%%-*}" || return 1
	MD_SRV_NAME="${2##*-}" || return 1
	MD_x509_SERIAL="${3}" || return 1
	MD_DATE="${4}" || return 1
	MD_CUSTOM_G="${5}" || return 1
	MD_NAME="${6}" || return 1
	MD_SUBKEY="${7}" || return 1
	MD_OPT="${8}" || return 1
	MD_FILTERS="${9}" || return 1
} # => metadata_string_to_vars ()

# Break metadata string at delimeter: New Newline, old space
# shellcheck disable=SC2034 # foo appears unused. Verify it or export it.
metadata_stov_safe ()
{
	[ -n "$1" ] || return 1
	input="$1"

	# Not using IFS
	err_msg="Unspecified delimiter"
	delim_save="${delimiter}"
	delimiter="${delimiter:-${newline}}"
	[ -n "${delimiter}" ] || return 1
	case "${input}" in
	*"${delimiter}"*) : ;;
	*) delimiter=' '
	esac

	MD_SEED="${input#*-}"

	# Expansions inside ${..} need to be quoted separately,
	# otherwise they will match as a pattern.
	# Which is the required behaviour.
	# shellcheck disable=SC2295
	{	# Required group for shellcheck
		m1="${input%%${delimiter}*}"
		input="${input#*${delimiter}}"
		m2="${input%%${delimiter}*}"
		input="${input#*${delimiter}}"
		m3="${input%%${delimiter}*}"
		input="${input#*${delimiter}}"
		m4="${input%%${delimiter}*}"
		input="${input#*${delimiter}}"
		m5="${input%%${delimiter}*}"
		input="${input#*${delimiter}}"
		m6="${input%%${delimiter}*}"
		input="${input#*${delimiter}}"
		m7="${input%%${delimiter}*}"
		input="${input#*${delimiter}}"
		m8="${input%%${delimiter}*}"
		input="${input#*${delimiter}}"
		m9="${input%%${delimiter}*}"
		input="${input#*${delimiter}}"
	}

	# An extra space has been used, probably in the name
	err_msg="metadata-lib: ${m9} vs ${input}"
	[ "${m9}" = "${input}" ] || return 1

	delimiter="${delim_save}"

	err_msg="metadata-lib: metadata_string_to_vars"
	metadata_string_to_vars "$m1" "$m2" "$m3" "$m4" \
		"$m5" "$m6" "$m7" "$m8" "$m9" || return 1
	unset -v m1 m2 m3 m4 m5 m6 m7 m8 m9 input err_msg
} # => metadata_stov_safe ()

#=# 70b4ec32-f1fc-47fb-a261-f02e7f572b62



# Generate CA Identity from CA fingerprint
generate_ca_identity ()
{
	# CA fingerprint
	easytls_ssl_generate_fingerprint "${ca_cert}" || {
		error_msg "generate_ca_identity - ca_fingerprint"
		return 1
		}
	ca_fingerprint="${certificate_fingerPrint}"

	# CA Identity
	ca_identity="$(easytls_generate_ca_identity "${ca_fingerprint}")" || {
		error_msg "generate_ca_identity - ca_identity"
		return 1
		}
	unset -v certificate_fingerPrint ca_fingerprint
} # => generate_ca_identity ()

# Collect CA Identity
easytls_generate_ca_identity ()
{
	"${EASYTLS_PRINTF}" '%s' "${1}" | \
		"${EASYTLS_SED}" -e 's/^.*=//g' -e 's/://g' || return 1
} # => easytls_ca_identity ()

# Save the ID
save_id ()
{
	# Verify Easy-TLS and Easy-RSA have been correctly setup
	ca_cert="${EASYRSA_PKI}/ca.crt"
	if [ -n "${EASYTLS_INIT}" ]; then
		# NOT fatal
		[ -f "${ca_cert}" ] || {
			warn "
      Easy-TLS cannot create TLS-Crypt-V2 keys until you create your CA
      and Server/Client certificates."
			return 1
			}

		# Clear the Old Master ID
		unset -v EASYTLS_MASTER_ID
	else
		# Fatal
		verify_ca_init
	fi

	# Generate CA Identity from CA fingerprint
	generate_ca_identity || die "save_id - generate_ca_identity"

	# Add CA-ID to config
	EASYTLS_verboff=1
	save_id_authorized=1
	# unset -v config_save_hash_block # Always allow save_id to update
	easytls_config id "${ca_identity}" || die "save_id - easytls_config"
	unset -v save_id_authorized #config_save_hash_block
	unset EASYTLS_verboff

	# Create CA-ID file - Legacy
	"${EASYTLS_PRINTF}" "%s" "${ca_identity}" > "${EASYTLS_CA_IDENTITY}" || \
		die "Failed to create CA Identify file: ${EASYTLS_CA_IDENTITY}"

	notice "Saved CA Identity: ${EASYTLS_CA_IDENTITY}"
	easytls_verbose
} # => save_id ()



############################################################################
#
# DISABLED-LIST Section
#

# Manage easytls/disabled-list.txt - Used by easytls-cryptv2-verify.sh
disabled_list_manager () {
	[ "$#" -ge 1 ] || die "Required option(s): disable|enable <NAME>"

	dl_action="${1}"; shift
	if [ -z "${1}" ]; then
		easytls_verbose
		"${EASYTLS_CAT}" "${EASYTLS_DISABLED_LIST}"
		easytls_verbose
		return 0
	fi
	name="${1}"; shift

	if [ -n "${EASYTLS_NO_CA}" ]; then
		cert_file="${EASYTLS_PKI}/${name}.crt"
	else
		cert_file="${EASYRSA_PKI}/issued/${name}.crt"
	fi

	tlskey_file="${EASYTLS_PKI}/${name}-tls-crypt-v2.key"
	metadata_file="${EASYTLS_META_DATA_D}/${name}-tls-crypt-v2.metadata"

	[ "${TLSKEY_SUBNAME}" = 'NOSUBKEY' ] || {
		full_name="${name}-${TLSKEY_SUBNAME}"
		tlskey_file="${EASYTLS_PKI}/${full_name}-tls-crypt-v2.key"
		metadata_file="${EASYTLS_META_DATA_D}/${full_name}-tls-crypt-v2.metadata"
		}

	# Check required files (No metadata_file for servers)
	[ -f "${tlskey_file}" ] || missing_file "${tlskey_file}"
	[ -f "${metadata_file}" ] || missing_file "${metadata_file}"

	# Get tlskey serial number from tlskey-index
	tlskey_serial="$(tlskey_index_fname_2_tlskser)"

	# Verify this tlskey-serial
	validate_hash "${tlskey_serial}" || {
		error_msg "disabled_list_manager - validate_hash failed: ${tlskey_serial}"
		unset -v tlskey_serial
		return 1
		}

	# Get x509 serial number from tlskey-index
	cert_serial="$(tlskey_index_fname_2_x509ser)" || \
		die "disabled_list_manager - cert_serial"

	# Verify this cert-serial
	verify_cert_serial "${cert_serial}" || \
		die "disabled_list_manager - verify_cert_serial"

	# Confirm remove
	confirm  "${action} tlskey file ? " "yes" "${action}: ${tlskey_file}"

	# Verify disabled-list Hash
	#disabled_list_verify_hash || die "Disabled list is corrupt."

	unset -v is_disabled is_enabled
	# Check disabled-list for this tlskey-serial
	if "${EASYTLS_GREP}" -q "^${tlskey_serial}[[:blank:]]" \
		"${EASYTLS_DISABLED_LIST}"
	then
		is_disabled=1
	else
		is_enabled=1
	fi

	# Update disabled-list
	case "${dl_action}" in
	disable) # Add serial number to the disabled-list
		if [ -n "${is_disabled}" ]; then
			: # serial is already disabled
		else
			new_record="${tlskey_serial} ${cert_serial} ${name} ${TLSKEY_SUBNAME}"
			if universal_update add "${EASYTLS_DISABLED_LIST}" "${new_record}"
			then
				disabled_list_updated=1
			else
				die "disabled_list_manager - disable"
			fi
		fi
	;;
	enable) # Remove serial number from the disabled-list
		if [ -n "${is_enabled}" ]; then
			: # serial is not disabled
		else
			old_record="${tlskey_serial}[[:blank:]]${cert_serial}[[:blank:]]"
			if universal_update del "${EASYTLS_DISABLED_LIST}" "${old_record}"
			then
				disabled_list_updated=1
			else
				die "disabled_list_manager - enable"
			fi
		fi
	;;
	*) die "disabled-list action invalid: ${action}"
	esac

	# Append sub-key-name
	[ "${TLSKEY_SUBNAME}" = 'NOSUBKEY' ] || name="${name}-${TLSKEY_SUBNAME}"

	# Update and/or Notify
	if [ -n "${disabled_list_updated}" ]; then
		notice "Updated disabled-list: ${name} ${dl_action}d"
		update_master_hash=1
	else
		notice "No change: ${name} is already ${dl_action}d"
		skip_master_hash=1
	fi
	easytls_verbose
} # => disabled_list_manager ()

# Verify current disable-list hash
disabled_list_verify_hash () {
	# DISABLED_DELETE
	die "DISABLED disabled_list_verify_hash"
	[ -z "${disabled_list_verify_hash_block}" ] || \
		die "tlskey index verify hash must only run once"
	request_fixed_hash=1
	generate_and_match_valid_hash \
		"${EASYTLS_DISABLED_LIST}" "${EASYTLS_DISABLED_HASH}" || {
			error_msg "disabled_list_verify_hash - fail"
			return 1
			}
	unset -v request_fixed_hash
	easytls_verbose "Disabled-list hash check OK"
	disabled_list_verify_hash_block=1
} # => disabled_list_verify_hash ()

# Save new disable-list hash
disabled_list_save_hash () {
	# DISABLED_DELETE
	die "DISABLED disabled_list_save_hash"
	[ -z "${disabled_list_save_hash_block}" ] || \
		die "disabled list save hash must only run once"
	request_fixed_hash=1
	generate_and_save_file_hash \
		"${EASYTLS_DISABLED_LIST}" "${EASYTLS_DISABLED_HASH}" || {
			error_msg "disabled_list_save_hash - fail"
			return 1
			}
	unset -v request_fixed_hash
	easytls_verbose "Disabled-list hash save OK"
	update_master_hash=1
	disabled_list_save_hash_block=1
} # => disabled_list_save_hash ()



############################################################################
#
# Inter-active Section - Work in progress
#

# Print with an extra newline
xaprint ()
{
	"${EASYTLS_PRINTF}" "\n%s\n%s\n" "# This can be changed" "${*}"
}
xdprint ()
{
	"${EASYTLS_PRINTF}" "\n%s\n%s\n" "# Do NOT change" "${*}"
}

# Ask a question
interactive_question ()
{
	unset -v ia_answer
	"$EASYTLS_PRINTF" '\n%s\n' "===================="
	"$EASYTLS_PRINTF" '\n%s\n' \
		'To cancel this inter-active menu at any time, press Control-C'
	[ -n "$ia_extra_help" ] && "$EASYTLS_PRINTF" '\n%s\n' "$ia_extra_help"
	"$EASYTLS_PRINTF" '%s\n' "$ia_question_help"
	# Get the answer
	interactive_wait_for_answer
	ia_answer="$user_input"
} # => interactive_question ()

# Wait for answer
interactive_wait_for_answer ()
{
	"$EASYTLS_PRINTF" '%s ' "$ia_question_text"
	unset -v user_input
	read -r user_input
} # => wait_for_answer ()

# Wait for answer
interactive_try_again ()
{
	"$EASYTLS_PRINTF" '%s ' "* Error: $1 - Press Enter to continue.."
	read -r user_input
	unset -v user_input
} # => wait_for_answer ()

# Show the command to run
interactive_show_cmd ()
{
	"$EASYTLS_PRINTF" '\n%s\n' '===================='
	"$EASYTLS_PRINTF" '\n%s\n' '* Easy-TLS command:'
	"$EASYTLS_PRINTF" '\n%s\n' "  * ./easytls $cmd_line"
	"$EASYTLS_PRINTF" '\n%s\n' '===================='
} # => interactive_show_cmd ()

# Inter-active build menu
interactive_build ()
{
	"$EASYTLS_PRINTF" '\n%s\n' 'Easy-TLS Inter-active TLS-key builder Menu.'

	# Capture the command line to build
	cmd_line='build'

	# Choose Key
	ia_question_help='
* Available TLS-key types:'
	ia_question_text='
  [1] TLS-Auth key                   - Legacy HMAC pre-shared key
  [2] TLS-Crypt-V1 key               - Basic TLS-crypt-v1 pre-shared key
  [3] TLS-Crypt-V2 key for Server    - Advanced TLS-Crypt-v2 Server key
  [4] TLS-Crypt-V2 key for Client    - Advanced TLS-Crypt-v2 Client key
  [5] TLS-Crypt-V2 GROUP Client key  - Advanced TLS-Crypt-v2 GROUP Client key

  Select the type of TLS-key to build:'
	interactive_question

	case "${ia_answer}" in
	1)	# Build TLS Auth
		"$EASYTLS_PRINTF" '\n%s\n' '* Build TLS-Auth key'
		cmd_line="${cmd_line}-tls-auth"
		interactive_show_cmd
		# Build key
		build_tls_auth || {
			error_msg '**Build TLS-Auth key failed'
			return 1
			}
	;;
	2)	# Build TLS Crypt v1
		"$EASYTLS_PRINTF" '\n%s\n' '* Build TLS-Crypt-V1 key'
		cmd_line="${cmd_line}-tls-crypt"
		interactive_show_cmd
		# Build key
		build_tls_crypt_v1 || {
			error_msg '**Build TLS-Crypt key failed'
			return 1
			}
	;;
	3)	# Build TLS Crypt v2 Server
		"$EASYTLS_PRINTF" '\n%s\n' '* Build TLS-Crypt-V2 key for Server'
		cmd_line="${cmd_line}-tls-crypt-v2-server"

		# Set Server name and verify cert type
		opt_server_name=""
		cert_type='Server'
		interactive_common_name
		opt_server_name="$common_name"

		# Print command
		interactive_show_cmd

		# Build key
		build_tls_crypt_v2_server "$opt_server_name" || {
			error_msg '**Build TLS-Crypt-V2 Server key failed'
			return 1
			}

		# save me! - Changes have been made
		update_master_hash=1
		save_master_hash || die "Master hash save failed"
		# Unset master_save_hash_block, master hash must run again on exit
		unset -v master_save_hash_block

		# Request inline
		cmd_line="inline-tls-crypt-v2 $opt_server_name"
		default='Y'
		ia_question_help="
* Do you want to build a corresponding inline-file ?

  * Default: $default"
		ia_question_text='
  Enter (y)es or (n)o:'
		while :
		do
			interactive_question
			ia_answer="${ia_answer:-$default}"
			case "$ia_answer" in
			Y|y|Yes|yes|YES)
				build_and_inline=1
				break
			;;
			N|n|No|no|NO)
				"$EASYTLS_PRINTF" '\n%s\n' \
					'  * TLS-Crypt-V2 Key built. Inline-file not created.'
				break
			;;
			*)	: # Ignore
			esac
		done

		# Invoke inline
		if [ -n "${build_and_inline}" ]; then
			# Set opt_no_key
			interactive_opt_no_key

			# Set opt_add_dh or opt_custom_dh
			default_dh_file="$EASYRSA_PKI/dh.pem"
			[ -n "${EASYTLS_NO_CA}" ] || {
				if [ -f "$default_dh_file" ]
				then
					# opt_add_dh
					interactive_opt_add_dh
				else
					# opt_custom_dh
					interactive_custom_dh
				fi
				}

			# Print command
			interactive_show_cmd

			# Create inline
			inline_tls_crypt_v2 "$opt_server_name" \
				"${opt_no_key}" "${opt_add_dh}" "${opt_custom_dh}" || {
					error_msg '**Create inline-file failed'
					return 1
					}
		fi

		return 0
	;;
	4|5)	# Build TLS Crypt v2 Client

		# shellcheck disable=SC2249 # (info): default *) case
		case "${ia_answer}" in
		4)
			"$EASYTLS_PRINTF" '\n%s\n' '* Build TLS-Crypt-V2 key for Client'
			unset -v group_key
		;;
		5)
			"$EASYTLS_PRINTF" '\n%s\n' '* Build TLS-Crypt-V2 GROUP key for Client'
			group_key=1
		esac

		cmd_line="${cmd_line}-tls-crypt-v2-client"

		# Set Server name
		opt_server_name=""
		cert_type='Server'
		ia_extra_help='* First, you MUST enter your *Server* commonName.'
		interactive_common_name
		unset -v ia_extra_help
		opt_server_name="$common_name"
		EASYTLS_PEER_FPR="${opt_server_name}"

		# Set Client name
		opt_client_name=""
		cert_type='Client'
		if [ -n "${group_key}" ]; then
			ia_extra_help='* Enter a unique name for your GROUP key. (eg. family)'
			interactive_gkey_name
			opt_client_name="$opt_gkey_name"
			easytls_cmd='build_tls_cv2_group_client'
		else
			ia_extra_help='* Now, enter your *Client* commonName.'
			interactive_common_name
			opt_client_name="$common_name"
			easytls_cmd='build_tls_crypt_v2_client'
		fi
		unset -v ia_extra_help

		# Either use default:EASYTLS or configured Custom-Group
		# If the user selects a new Custom-Group here then this
		# key is likely to fail to connect because Custom-Group
		# will not match what the server expects.
		interactive_custom_group

		# Set option --sub-key-name
		interactive_sub_key_name

		# Set hardware addresses
		interactive_hwaddr || {
			error_msg "Invalid hardware-address: $ia_answer"
			return 1
			}

		# Print command
		interactive_show_cmd

		# Enable internal require_batch
		require_batch=1

		# Build key
		# shellcheck disable=SC2086 # Require word split: opt_hardware_list
		"${easytls_cmd}" "$opt_server_name" "$opt_client_name" \
			${opt_hardware_list} || {
				error_msg '**Build TLS-CryptV2 Client key failed'
				return 1
				}

		# Disable internal require_batch
		unset -v require_batch

		# save me! - Changes have been made
		update_master_hash=1
		save_master_hash || die "Master hash save failed"
		# Unset master_save_hash_block, master hash must run again on exit
		unset -v master_save_hash_block

		# Request inline
		cmd_line="inline-tls-crypt-v2 $opt_client_name"
		default='Y'
		ia_question_help="
* Do you want to build a corresponding inline-file ?

  * Default: $default"
		ia_question_text='
  Enter (y)es or (n)o:'
		while :
		do
			interactive_question
			ia_answer="${ia_answer:-$default}"
			case "$ia_answer" in
			Y|y|Yes|yes|YES)
				build_and_inline=1
				break
			;;
			N|n|No|no|NO)
				"$EASYTLS_PRINTF" '\n%s\n' \
					'  * TLS-Crypt-V2 Key built. Inline-file not created.'
				break
			;;
			*)	: # Ignore
			esac
		done

		# Invoke inline
		if [ -n "${build_and_inline}" ]; then
			if [ -n "${group_key}" ]; then
				ia_extra_help='* Now, enter your *Client* commonName.'
				interactive_common_name
				opt_client_name="$common_name"
				unset -v ia_extra_help
				easytls_cmd='inline_tls_cv2_group_client'
			else
				easytls_cmd='inline_tls_crypt_v2'
			fi

			# Set opt_no_key
			interactive_opt_no_key

			# Set opt_no_md
			interactive_opt_no_md

			# Set opt_add_hw
			interactive_opt_add_hw

			# Print command
			interactive_show_cmd

			# Create inline
			"${easytls_cmd}" "$opt_client_name" "$opt_gkey_name" \
				"${opt_no_key}" "${opt_no_md}" "${opt_add_hw}" || {
					error_msg '**Create inline-file failed'
					return 1
					}
		fi
		#return 0
	;;
	*)	# Other value -> Quit
		"$EASYTLS_PRINTF" '\n%s\n' '  Quit!'
		skip_master_hash=1
	esac
} # => interactive_build ()

# Inter-active inline menu
interactive_inline ()
{
	"$EASYTLS_PRINTF" '\n%s\n' 'Easy-TLS Inter-active Inline-file builder Menu.'

	# Capture the command line to inline
	cmd_line='inline'

	# Choose Key
	ia_question_help='
* Available Inline-file types:'
	ia_question_text='
  [1] Inline-file with TLS-Auth key for Server
  [2] Inline-file with TLS-Auth key for Client
  [3] Inline-file with TLS-Crypt-V1 key for Server
  [4] Inline-file with TLS-Crypt-V1 key for Client
  [5] Inline-file with TLS-Crypt-V2 Server key
  [6] Inline-file with TLS-Crypt-V2 Client key
  [7] Inline-file with TLS-Crypt-V2 Client GROUP key

  Select the type of Inline-file to build:'
	interactive_question

	case "${ia_answer}" in
	1|2)
		# Inline TLS Auth Server|Client
		cert_type='Client'
		[ "${ia_answer}" = '1' ] && cert_type='Server'

		"${EASYTLS_PRINTF}" '\n%s\n' "* Create TLS-Auth ${cert_type} inline-file"
		cmd_line="${cmd_line}-tls-auth"

		# Set commonName
		interactive_common_name
		opt_common_name="${common_name}"

		# Set key_dir
		interactive_key_direction

		# Set opt_no_key
		interactive_opt_no_key

		# Set opt_add_dh
		default_dh_file="${EASYRSA_PKI}/dh.pem"
		[ -n "${EASYTLS_NO_CA}" ] || {
			if [ -f "${default_dh_file}" ]
			then
				[ "${cert_type}" = 'Server' ] && interactive_opt_add_dh
			else
				[ "${cert_type}" = 'Server' ] && interactive_custom_dh
			fi
			}

		# share fingerprint
		[ -n "${EASYTLS_NO_CA}" ] && [ "${cert_type}" = 'Client' ] && \
			interactive_selfsign_peer

		# Print command
		interactive_show_cmd

		# Build .inline
		inline_tls_auth "${opt_common_name}" "${key_direction}" \
			"${opt_no_key}" "${opt_add_dh}" "${opt_custom_dh}" || {
				error_msg '**Create inline-file failed'
				return 1
				}
	;;
	3|4)
		# Inline TLS Crypt v1 Server or Client
		cert_type='Client'
		[ "${ia_answer}" = '3' ] && cert_type='Server'

		"${EASYTLS_PRINTF}" '\n%s\n' "* Create TLS-Crypt-V1 $cert_type inline-file"
		cmd_line="${cmd_line}-tls-crypt"

		# Set commonName
		interactive_common_name
		opt_common_name="${common_name}"

		# Set opt_no_key
		interactive_opt_no_key

		# Set opt_add_dh
		default_dh_file="${EASYRSA_PKI}/dh.pem"
		[ -n "${EASYTLS_NO_CA}" ] || {
			if [ -f "${default_dh_file}" ]
			then
				[ "${cert_type}" = 'Server' ] && interactive_opt_add_dh
			else
				[ "${cert_type}" = 'Server' ] && interactive_custom_dh
			fi
			}

		# share fingerprint
		[ -n "${EASYTLS_NO_CA}" ] && [ "${cert_type}" = 'Client' ] && \
			interactive_selfsign_peer

		# Print command
		interactive_show_cmd

		# Build .inline
		inline_tls_crypt_v1 "${opt_common_name}" \
			"${opt_no_key}" "${opt_add_dh}" "${opt_custom_dh}" || {
				error_msg '**Create inline-file failed'
				return 1
				}
	;;
	5)	# Inline TLS Crypt v2 Server
		"${EASYTLS_PRINTF}" '\n%s\n' '* Create TLS-Crypt-v2 Server inline-file'
		cmd_line="${cmd_line}-tls-crypt-v2"
		cert_type="Server"

		# Set commonName
		interactive_common_name
		opt_common_name="${common_name}"

		# Verify purpose
		interactive_verify_cert

		# Set opt_no_key
		interactive_opt_no_key

		# Set opt_add_dh
		default_dh_file="${EASYRSA_PKI}/dh.pem"
		if [ -f "${default_dh_file}" ]
		then
			interactive_opt_add_dh
		else
			interactive_custom_dh
		fi

		# Print command
		interactive_show_cmd

		# Build .inline
		inline_tls_crypt_v2 "${opt_common_name}" \
			"${opt_no_key}" "${opt_add_dh}" || {
				error_msg '**Create inline-file failed'
				return 1
				}
	;;
	6|7)	# Inline TLS Crypt v2 Client

		# shellcheck disable=SC2249 # (info): default *) case
		case "${ia_answer}" in
		6)
			"$EASYTLS_PRINTF" '\n%s\n' \
				'* Create TLS-Crypt-v2 Client inline-file'
			unset -v group_key
		;;
		7)
			"$EASYTLS_PRINTF" '\n%s\n' \
				'* Create TLS-Crypt-v2 Client GROUP key inline-file'
			group_key=1
		esac

		"${EASYTLS_PRINTF}" '\n%s\n' ''
		cmd_line="${cmd_line}-tls-crypt-v2"
		cert_type="Client"

		# Set commonName
		ia_extra_help='* First, enter your *Client* commonName.'
		interactive_common_name
		opt_common_name="$common_name"
		easytls_cmd='inline_tls_crypt_v2'
		if [ -n "${group_key}" ]
		then
			ia_extra_help='* Enter the unique name of your GROUP key. (eg. family)'
			interactive_gkey_name
			#opt_client_name="$opt_gkey_name"
			easytls_cmd='inline_tls_cv2_group_client'
		fi
		unset -v ia_extra_help

		# Verify purpose
		interactive_verify_cert

		# Set option --sub-key-name
		interactive_sub_key_name

		# Set opt_no_key
		interactive_opt_no_key

		# Set opt_no_md
		interactive_opt_no_md

		# Set opt_add_hw
		interactive_opt_add_hw

		# share fingerprint
		[ -n "${EASYTLS_NO_CA}" ] && interactive_selfsign_peer

		# Print command
		interactive_show_cmd

		# Build .inline
		"${easytls_cmd}" "${opt_common_name}" "${opt_gkey_name}" \
			"${opt_no_key}" "${opt_no_md}" "${opt_add_hw}" || {
				error_msg '**Create inline-file failed'
				return 1
				}
	;;
	*)	# Other value -> Quit
		"${EASYTLS_PRINTF}" '\n%s\n' '  Quit!'
		skip_master_hash=1
	esac
} # => interactive_inline ()

# Inter-active Remove menu
interactive_remove ()
{
	"$EASYTLS_PRINTF" '\n%s\n' 'Easy-TLS Inter-active Remove Menu.'

	# Capture the command line to inline
	cmd_line='remove'

	# Choose Key
	ia_question_help='
* Available file types:'
	ia_question_text='
  [1] TLS-Crypt-V2 Client inline-file
  [2] TLS-Crypt-V2 Client key
  [3] TLS-Crypt-V2 GROUP Client inline-file
  [4] TLS-Crypt-V2 GROUP Client key
  [5] TLS-Crypt-V2 Server inline-file
  [6] TLS-Crypt-V2 Server key
  [7] TLS-Crypt-V1 key
  [8] TLS-Auth key

  Select the type of file to remove:'
	interactive_question

	case "${ia_answer}" in
	1)	# Remove Client inline-file
		"$EASYTLS_PRINTF" '\n%s\n' '* Remove TLS-Crypt-v2 Client inline-file'
		cmd_line="${cmd_line}-inline"

		# Set commonName
		opt_name=""
		cert_type='Client'
		interactive_common_name
		opt_name="$common_name"
		inline_file="${EASYTLS_PKI}/${opt_name}.inline"
		key_file="${EASYTLS_PKI}/${opt_name}-tls-crypt-v2.key"
		metadata_debug="${EASYTLS_META_DATA_D}/${opt_name}-tls-crypt-v2.metadata"

		# Set option --sub-key-name
		interactive_sub_key_name

		# Confirm
		interactive_remove_warning 'remove-tls-crypt-v2-client-inline'

		# Print command
		interactive_show_cmd

		# Enable internal require_batch
		require_batch=1

		# Remove .inline
		remove_inline "$opt_name" || {
			error_msg '**Remove inline-file failed'
			return 1
			}

		# Disable internal require_batch
		unset -v require_batch

		"$EASYTLS_PRINTF" '\n%s\n' \
			"  * TLS-Crypt-V2 Client inline removed: $key_file"

		# save me! - Changes have been made
		update_master_hash=1
		save_master_hash || die "Master hash save failed"
		# Unset master_save_hash_block, master hash must run again on exit
		unset -v master_save_hash_block

		# Request remove_tlskey
		default='Y'
		ia_question_help="
* Do you want to remove the corresponding TLS-Crypt-V2 key ?

  * Default: $default"
		ia_question_text='
  Enter (y)es or (n)o:'
		while :
		do
			interactive_question
			ia_answer="${ia_answer:-$default}"
			case "$ia_answer" in
			Y|y|Yes|yes|YES)
				remove_inline_and_tlskey=1
				break
			;;
			N|n|No|no|NO)
				break
			;;
			*)	: # Ignore
			esac
		done

		# Invoke remove_tlskey
		if [ -n "${remove_inline_and_tlskey}" ]
		then
			# Print command
			cmd_line="remove-tlskey $opt_name"
			[ "$TLSKEY_SUBNAME" = 'NOSUBKEY' ] || \
				cmd_line="--sub-key-name=${sub_key_name} ${cmd_line}"
			interactive_show_cmd

			# Enable internal require_batch
			require_batch=1

			# Remove TLS key
			remove_tlskey "$opt_name" || {
				error_msg '**Remove TLS-key failed'
				return 1
				}

			# Disable internal require_batch
			unset -v require_batch

			"$EASYTLS_PRINTF" '\n%s\n' \
				"  * TLS-Crypt-V2 Client key removed: $key_file"
		else
			"$EASYTLS_PRINTF" '\n%s\n' \
			'  * Inline-file removed. TLS-Crypt-V2 key is unchanged.'
			easytls_verbose
		fi
	;;
	2)	# Remove client key
		"$EASYTLS_PRINTF" '\n%s\n' '* Remove TLS-Crypt-V2 Client key'
		cmd_line="${cmd_line}-tlskey"

		# Set Client name
		opt_name=""
		cert_type='Client'
		interactive_common_name
		opt_name="$common_name"

		# Set option --sub-key-name
		interactive_sub_key_name

		# Append --sub-key-name if specifies
		if [ "$TLSKEY_SUBNAME" = 'NOSUBKEY' ]
		then
			key_name="${opt_name}"
		else
			key_name="${opt_name}-$TLSKEY_SUBNAME"
		fi

		# Set file names
		key_file="${EASYTLS_PKI}/${key_name}-tls-crypt-v2.key"
		metadata_debug="${EASYTLS_META_DATA_D}/${key_name}-tls-crypt-v2.metadata"

		# Confirm
		interactive_remove_warning 'remove-tls-crypt-v2-client-key'

		# Print command
		interactive_show_cmd

		# Enable internal require_batch
		require_batch=1

		# Remove key
		remove_tlskey "$opt_name" || {
			error_msg '**Remove TLS-key failed'
			return 1
			}

		# Disable internal require_batch
		unset -v require_batch

		"$EASYTLS_PRINTF" '\n%s\n' \
			"  * TLS-Crypt-V2 Client key removed: $key_file"
	;;
	3)	# Remove GROUP Client inline-file
		"$EASYTLS_PRINTF" '\n%s\n' '* Remove TLS-Crypt-v2 Client inline-file'
		cmd_line="${cmd_line}-group-inline"

		# Set commonName
		opt_name=""
		cert_type='Client'
		interactive_common_name
		opt_name="$common_name"
		inline_file="${EASYTLS_PKI}/${opt_name}.inline"
		key_file="${EASYTLS_PKI}/${opt_name}-tls-crypt-v2.key"
		metadata_debug="${EASYTLS_META_DATA_D}/${opt_name}-tls-crypt-v2.metadata"

		# Group name
		interactive_gkey_name
		inline_file="${EASYTLS_PKI}/${opt_name}-${opt_gkey_name}.inline"
		opt_name="${opt_name}-${opt_gkey_name}"

		# Set option --sub-key-name
		#interactive_sub_key_name

		# Confirm
		interactive_remove_warning 'remove-tls-crypt-v2-group-client-inline'

		# Print command
		interactive_show_cmd

		# Enable internal require_batch
		require_batch=1

		# Remove .inline
		remove_inline "$opt_name" || {
			error_msg '**Remove inline-file failed'
			return 1
			}

		# Disable internal require_batch
		unset -v require_batch

		"$EASYTLS_PRINTF" '\n%s\n' \
			"  * TLS-Crypt-V2 Client inline removed: $key_file"

		# save me! - Changes have been made
		update_master_hash=1
		save_master_hash || die "Master hash save failed"
		# Unset master_save_hash_block, master hash must run again on exit
		unset -v master_save_hash_block

		# Request remove_tlskey
		default='Y'
		ia_question_help="
* Do you want to remove the corresponding TLS-Crypt-V2 key ?

  * Default: $default"
		ia_question_text='
  Enter (y)es or (n)o:'
		while :
		do
			interactive_question
			ia_answer="${ia_answer:-$default}"
			case "$ia_answer" in
			Y|y|Yes|yes|YES)
				remove_inline_and_tlskey=1
				break
			;;
			N|n|No|no|NO)
				break
			;;
			*)	: # Ignore
			esac
		done

		# Invoke remove_tlskey
		if [ -n "${remove_inline_and_tlskey}" ]
		then
			# Print command
			cmd_line="remove-group-tlskey $opt_name"
			[ "$TLSKEY_SUBNAME" = 'NOSUBKEY' ] || \
				cmd_line="--sub-key-name=${sub_key_name} ${cmd_line}"
			interactive_show_cmd

			# Enable internal require_batch
			require_batch=1

			# Remove TLS key
			remove_group_tlskey "$opt_gkey_name" || {
				error_msg '**Remove TLS-key failed'
				return 1
				}

			# Disable internal require_batch
			unset -v require_batch

			"$EASYTLS_PRINTF" '\n%s\n' \
				"  * TLS-Crypt-V2 GROUP Client key removed: $key_file"
		else
			"$EASYTLS_PRINTF" '\n%s\n' \
			'  * Inline-file removed. TLS-Crypt-V2 GROUP key is unchanged.'
			easytls_verbose
		fi
	;;
	4)	# Remove GROUP client key
		"$EASYTLS_PRINTF" '\n%s\n' '* Remove TLS-Crypt-V2 GROUP Client key'
		cmd_line="${cmd_line}-tlskey"

		# Group name
		opt_name=""
		interactive_gkey_name
		key_file="${EASYTLS_PKI}/${opt_gkey_name}-tls-crypt-v2.key"
		opt_name="${opt_gkey_name}"

		# Set Client name
		#opt_name=""
		#cert_type='Client'
		#interactive_common_name
		#opt_name="$common_name"

		# Set option --sub-key-name
		#interactive_sub_key_name

		# Append --sub-key-name if specifies
		if [ "$TLSKEY_SUBNAME" = 'NOSUBKEY' ]
		then
			key_name="${opt_name}"
		else
			key_name="${opt_name}-$TLSKEY_SUBNAME"
		fi

		# Confirm
		interactive_remove_warning 'remove-tls-crypt-v2-group-client-key'

		# Print command
		interactive_show_cmd

		# Enable internal require_batch
		require_batch=1

		# Remove key
		remove_group_tlskey "$opt_name" || {
			error_msg '**Remove GROUP TLS-key failed'
			return 1
			}

		# Disable internal require_batch
		unset -v require_batch

		"$EASYTLS_PRINTF" '\n%s\n' \
			"  * TLS-Crypt-V2 GROUP Client key removed: $key_file"
	;;
	5)	# Remove Server inline-file
		"$EASYTLS_PRINTF" '\n%s\n' '* Remove TLS-Crypt-v2 Server inline-file'
		cmd_line="${cmd_line}-inline"

		# Set commonName
		opt_name=""
		cert_type='Server'
		interactive_common_name
		opt_name="$common_name"
		unset -v ia_extra_help

		inline_file="${EASYTLS_PKI}/${opt_name}.inline"
		[ -f "$inline_file" ] || missing_file "$inline_file"

		# Confirm
		interactive_remove_warning 'remove-tls-crypt-v2-server-inline'

		# Print command
		interactive_show_cmd

		# Enable internal require_batch
		require_batch=1

		# Remove .inline
		remove_inline "$opt_name" || {
				error_msg '**Remove inline-file failed'
				return 1
				}

		# Disable internal require_batch
		unset -v require_batch

		# save me! - Changes have been made
		update_master_hash=1
		save_master_hash || die "Master hash save failed"
		# Unset master_save_hash_block, master hash must run again on exit
		unset -v master_save_hash_block

		# Request remove_tlskey
		cmd_line="remove-tlskey $opt_name"
		default='Y'
		ia_question_help="
* Do you want to remove the corresponding TLS-Crypt-V2 key ?

  * Default: $default"
		ia_question_text='
  Enter (y)es or (n)o:'
		while :
		do
			interactive_question
			ia_answer="${ia_answer:-$default}"
			case "$ia_answer" in
			Y|y|Yes|yes|YES)
				remove_inline_and_tlskey=1
				break
			;;
			N|n|No|no|NO)
				break
			;;
			*)	: # Ignore
			esac
		done

		# Invoke remove_tlskey
		if [ -n "${remove_inline_and_tlskey}" ]
		then
		key_file="${EASYTLS_PKI}/${opt_name}-tls-crypt-v2.key"
		[ -f "$key_file" ] || missing_file "$key_file"

		# Confirm
		confirm 'Are you sure you want to delete this key (CANNOT be undone): ' \
			'yes' "
* If you remove a TLS-Crypt-V2 Server key which has been used
  to build any TLS-Crypt-V2 Client keys then those Client keys
  and their corresponding inline files will all be invalid.

  Key file:
  * $key_file"

			# Print command
			interactive_show_cmd

			# Enable internal require_batch
			require_batch=1

			# Remove TLS key
			remove_tlskey "$opt_name" || {
				error_msg '**Remove TLS-key failed'
				return 1
				}

			# Disable internal require_batch
			unset -v require_batch
		else
			"$EASYTLS_PRINTF" '\n%s\n' \
				'  * Inline-file removed. TLS-Crypt-V2 key is unchanged.'
			easytls_verbose
		fi
	;;
	6)	# Remove Server key
		"$EASYTLS_PRINTF" '\n%s\n' '* Remove TLS-Crypt-V2 Server key'
		cmd_line="${cmd_line}-tlskey"

		# Set Server name
		opt_name=""
		cert_type='Server'
		interactive_common_name
		opt_name="$common_name"

		key_file="${EASYTLS_PKI}/${opt_name}-tls-crypt-v2.key"
		[ -f "$key_file" ] || missing_file "$key_file"

		# Confirm
		interactive_remove_warning 'remove-tls-crypt-v2-server-key'

		# Print command
		interactive_show_cmd

		# Enable internal require_batch
		require_batch=1

		# Remove key
		remove_tlskey "$opt_name" || {
			error_msg '**Remove TLS-Crypt-V2 key failed'
			return 1
			}

		# Disable internal require_batch
		unset -v require_batch

		"$EASYTLS_PRINTF" '\n%s\n' \
			"  * TLS-Crypt-V2 Server key removed: $key_file"
	;;
	7)	# tls-crypt key
		"$EASYTLS_PRINTF" '\n%s\n' '* Remove TLS-Crypt key'
		cmd_line="${cmd_line}-tlskey"

		key_file="${EASYTLS_PKI}/tls-crypt.key"
		[ -f "$key_file" ] || missing_file "$key_file"

		# Confirm
		interactive_remove_warning 'remove-tls-crypt-key'

		# Print command
		interactive_show_cmd

		# Enable internal require_batch
		require_batch=1

		"$EASYTLS_RM" "$key_file" || {
			error_msg '**Remove TLS-Crypt key failed'
			return 1
			}

		# Disable internal require_batch
		unset -v require_batch

		"$EASYTLS_PRINTF" '\n%s\n' "  * TLS-Crypt key removed: $key_file"
	;;
	8)	# tls-auth key
		"$EASYTLS_PRINTF" '\n%s\n' '* Remove TLS-Auth key'
		cmd_line="${cmd_line}-tlskey"

		key_file="${EASYTLS_PKI}/tls-auth.key"
		[ -f "$key_file" ] || missing_file "$key_file"

		# Confirm
		interactive_remove_warning 'remove-tls-auth-key'

		# Print command
		interactive_show_cmd

		# Enable internal require_batch
		require_batch=1

		"$EASYTLS_RM" "$key_file" || {
			error_msg '**Remove TLS-Auth key failed'
			return 1
			}

		# Disable internal require_batch
		unset -v require_batch

		"$EASYTLS_PRINTF" '\n%s\n' "  * TLS-Auth key removed: $key_file"
	;;
	*)	# Other value -> Quit
		"$EASYTLS_PRINTF" '\n%s\n' '  Quit!'
		skip_master_hash=1
	esac
} # => interactive_remove ()

# Issue a remove warning
interactive_remove_warning ()
{
	[ -n "${EASYTLS_BATCH}" ] || \
		"$EASYTLS_PRINTF" '\n%s\n%s\n%s' \
			'  ###########' '  # WARNING #' '  ###########'

	case "${1}" in
	remove-tls-auth-key)
	# TLS-Auth key
		[ -f "$key_file" ] || missing_file "$key_file"
		confirm 'Are you sure you want to delete this key (CANNOT be undone): ' \
			'yes' "
* If you remove a TLS-Auth key which has been used to build any
  inline-files then those inline-files will all be invalid.

  Key file:
  * $key_file"
	;;
	remove-tls-crypt-key)
		# TLS-Crypt key
		[ -f "$key_file" ] || missing_file "$key_file"
		confirm 'Are you sure you want to delete this key (CANNOT be undone): ' \
			'yes' "
* If you remove a TLS-Crypt key which has been used to build any
  inline-files then those inline-files will all be invalid.

  Key file:
  * $key_file"
	;;
	remove-tls-crypt-v2-server-key)
		# TLS-Crypt-V2 Server key
		[ -f "$key_file" ] || missing_file "$key_file"
		confirm 'Are you sure you want to delete this key (CANNOT be undone): ' \
			'yes' "
* If you remove a TLS-Crypt-V2 Server key which has been used
  to build any TLS-Crypt-V2 Client keys then those Client keys
  and their corresponding inline files will all be invalid.

  Key file:
  * $key_file"
	;;
	remove-tls-crypt-v2-client-key)
		# TLS-Crypt-V2 Client key
		[ -f "$key_file" ] || missing_file "$key_file"
		confirm 'Are you sure you want to delete this key (CANNOT be undone): ' \
			'yes' "
* If you remove a TLS-Crypt-V2 Client key then you can easily
  build a new one from the same TLS-Crypt-V2 Server key.

  Key file:
  * $key_file"
	;;
	remove-tls-crypt-v2-group-client-key)
		# TLS-Crypt-V2 Client key
		[ -f "$key_file" ] || missing_file "$key_file"
		confirm 'Are you sure you want to delete this key (CANNOT be undone): ' \
			'yes' "
* If you remove a TLS-Crypt-V2 GROUP Client key then you can easily
  build a new one from the same TLS-Crypt-V2 Server key.

  Key file:
  * $key_file"
	;;
	remove-tls-auth-inline)
		# TLS-Auth inline
		[ -f "$inline_file" ] || missing_file "$inline_file"
		confirm 'Are you sure you want to delete this inline-file: ' \
			'yes' "
* When you remove an inline-file you can easily build a new one.

  Inline file:
  * $inline_file"
	;;
	remove-tls-crypt-inline)
		# TLS-Crypt inline
		[ -f "$inline_file" ] || missing_file "$inline_file"
		confirm 'Are you sure you want to delete this inline-file: ' \
			'yes' "
* When you remove an inline-file you can easily build a new one.

  Inline file:
  * $inline_file"
	;;
	remove-tls-crypt-v2-server-inline)
		# TLS-Crypt-V2 Server inline
		[ -f "$inline_file" ] || missing_file "$inline_file"
		confirm 'Are you sure you want to delete this inline-file: ' \
			'yes' "
* When you remove an inline-file you can easily build a new one.

  Inline file:
  * $inline_file"
	;;
	remove-tls-crypt-v2-client-inline)
		# TLS-Crypt-V2 Client inline
		[ -f "$inline_file" ] || missing_file "$inline_file"
		confirm 'Are you sure you want to delete this inline-file: ' \
			'yes' "
* When you remove an inline-file you can easily build a new one.

  Inline file:
  * $inline_file"
	;;
	remove-tls-crypt-v2-group-client-inline)
		# TLS-Crypt-V2 Client inline
		[ -f "$inline_file" ] || missing_file "$inline_file"
		confirm 'Are you sure you want to delete this inline-file: ' \
			'yes' "
* When you remove an inline-file you can easily build a new one.

  Inline file:
  * $inline_file"
	;;
	*)	die "Cannot issue a warning for unknown option: $1"
	esac
} # => interactive_remove_warning ()

# Configure scripts
interactive_scripts ()
{
	"$EASYTLS_PRINTF" '\n%s\n' 'Easy-TLS Inter-active Script Configuration Menu.'

	# Capture the command line to inline
	cmd_line=''

	# Choose Key
	ia_question_help='
* Available script which can be configured:'
	ia_question_text='
  [1] TLS Crypt v2 Verify (easytls-cryptv2-verify.sh)
      (Includes hwaddr verification script: easytls-cryptv2-client-connect.sh)

  Select the type of script to configure:'
	interactive_question

	case "${ia_answer}" in
	1)	# easytls-cryptv2-verify.sh

		[ -f "$EASYTLS_VERIFY_V2_SH" ] || {
			help_note='Download from: https://github.com/TinCanTech/easy-tls'
			warn "Required script missing: $EASYTLS_VERIFY_V2_SH"
			}

		[ -f "$EASYTLS_CLICON_SH" ] || {
			help_note='Download from: https://github.com/TinCanTech/easy-tls'
			warn "Required script missing: $EASYTLS_CLICON_SH"
			}

		# no-ca mode - opt_no_ca='-z'
		automatic_no_ca

		# -c|--ca=<path> - opt_ca_path
		interactive_ca_path

		# Hash algorithm
		interactive_hashalgo

		# -g|--custom-group=<GROUP> - opt_customg
		interactive_custom_group

		# -x|--max-tls-age - opt_key_age
		interactive_key_age

		# -d|--disable-list - opt_disable_list
		interactive_disabled_list

		# --v1|--via-crl
		# --v2|--via-ca
		# --v3|--via-index
		# opt_x509_method
		# opt_load_ca_id
		# opt_preload_id
		interactive_x509_method

		# Openvpn PID - opt_client_connect
		interactive_client_connect

		if [ -n "${opt_client_connect}" ]
		then
			# -t|--tmp-dir - opt_tmp_dir
			interactive_tmp_dir

			# --script-security
			interactive_script_security

			# -a|-p|-c|-k hardware requirements
			interactive_hw_required

			# -a|-p|-c|-k hardware requirements
			interactive_ip_filter

			# Kill-client mode
			interactive_kill_client
		fi

		# Create vars files
		{	# easytls-cryptv2-verify.vars
			print "# easytls-cryptv2-verify.vars"
			xaprint "EASYTLS_VERBOSE=1"
			xaprint "ENABLE_KILL_CLIENT=1"
			xaprint "TLSKEY_MAX_AGE=${key_age_days}"
			xaprint "#unset use_disable_list"
			print "# This means the disabled-list will be used"
			xdprint "CA_DIR=\"${opt_ca_path}\""
			[ -z "${opt_no_ca}" ] || xdprint "EASYTLS_NO_CA=1"
			xdprint "LOCAL_CUSTOM_G=\"${opt_customg}\""
			[ -z "${X509_METHOD}" ] || xaprint "X509_METHOD=${X509_METHOD}"
			xdprint "EASYTLS_tmp_dir=\"${opt_tmp_dir}\""
		} > "${EASYTLS_VERIFY_V2_VARS}" || die "Failed to create vars file"

		{	# easytls-client-connect.vars
			print "# easytls-client-connect.vars"
			xaprint "EASYTLS_VERBOSE=1"
			xaprint "#allow_no_check=1"
			xaprint "#ignore_x509_mismatch=1"
			print "# Must also be set in easytls-verify.vars"
			xaprint "#push_hwaddr_required=1"
			xaprint "#crypt_v2_required=1"
			xaprint "#key_hwaddr_required=1"
			xaprint "#PEER_IP_MATCH=1"
			xaprint "ENABLE_CONN_TRAC=1"
			xaprint "VERBOSE_CONN_TRAC=1"
			xdprint "EASYTLS_tmp_dir=\"${opt_tmp_dir}\""
		} > "${EASYTLS_CLICON_VARS}" || die "Failed to create vars file"

		{	# easytls-client-disconnect.vars
			print "# easytls-client-disconnect.vars"
			xaprint "EASYTLS_VERBOSE=1"
			xaprint "ENABLE_CONN_TRAC=1"
			xaprint "VERBOSE_CONN_TRAC=1"
			xdprint "EASYTLS_tmp_dir=\"${opt_tmp_dir}\""
		} > "${EASYTLS_CLIDIS_VARS}" || die "Failed to create vars file"

		# Create config
		{
			# easytls-cryptv2-verify.sh
			"$EASYTLS_PRINTF" '%s\n\n' '# Easy-TLS script configuration'
			"$EASYTLS_PRINTF" '%s\n\n' "tmp-dir '${opt_tmp_dir}'"
			"$EASYTLS_PRINTF" '%s\n\n' "tls-export-cert '${opt_tmp_dir}'"
			"$EASYTLS_PRINTF" '%s\n' \
				'# If your clients have username/password then set this to level 3'
			"$EASYTLS_PRINTF" '%s\n\n' "script-security ${opt_script_security}"
			"$EASYTLS_PRINTF" '%s'   'tls-crypt-v2-verify'
			"$EASYTLS_PRINTF" ' %s'  "'"
			[ -n "${EASYTLS_FOR_WINDOWS}" ] && "$EASYTLS_PRINTF" "%s " "$EASYTLS_SH_EXE"
			"$EASYTLS_PRINTF" '%s'   "$EASYTLS_VERIFY_V2_SH"
			"$EASYTLS_PRINTF" ' %s'  "-s=${EASYTLS_VERIFY_V2_VARS}"
			[ "$opt_no_ca" ]       && "$EASYTLS_PRINTF" ' %s' "${opt_no_ca}"
			"$EASYTLS_PRINTF" ' %s'  "-c=${opt_ca_path}"
			[ "$opt_customg" ]      && "$EASYTLS_PRINTF" ' %s' "-g=${opt_customg}"
			[ "$opt_hashalgo" ]     && "$EASYTLS_PRINTF" ' %s' "${opt_hashalgo}"
			#[ "$opt_key_age" ]      && "$EASYTLS_PRINTF" ' %s' "${opt_key_age}"
			#[ "$opt_disable_list" ] && "$EASYTLS_PRINTF" ' %s' "${opt_disable_list}"
			#[ "$opt_kill_client" ]  && "$EASYTLS_PRINTF" ' %s' "${opt_kill_client}"
			#[ "$opt_tmp_dir" ]      && "$EASYTLS_PRINTF" ' %s' "-t=${opt_tmp_dir}"
			[ "$opt_x509_method" ]  && "$EASYTLS_PRINTF" ' %s' "${opt_x509_method}"
			[ "$opt_load_ca_id" ]   && "$EASYTLS_PRINTF" ' %s' "${opt_load_ca_id}"
			[ "$opt_preload_id" ]   && "$EASYTLS_PRINTF" ' %s' "${opt_preload_id}"
			"$EASYTLS_PRINTF" '%s\n\n' "'"

			# easytls-client-connect.sh
			[ -n "${opt_client_connect}" ] && {
				"$EASYTLS_PRINTF" '%s' 'client-connect'
				"$EASYTLS_PRINTF" ' %s' "'"
				[ -n "${EASYTLS_FOR_WINDOWS}" ] && \
					"$EASYTLS_PRINTF" "%s " "$EASYTLS_SH_EXE"
				"$EASYTLS_PRINTF" '%s' "${EASYTLS_CLICON_SH} -v"
				"$EASYTLS_PRINTF" ' %s' "-s=${EASYTLS_CLICON_VARS}"
				[ "$opt_hw_required" ] && \
					"$EASYTLS_PRINTF" ' %s' "$opt_hw_required"
				[ "$opt_ip_filter" ] && "$EASYTLS_PRINTF" ' %s' "-i"
				[ "$opt_tmp_dir" ] && "$EASYTLS_PRINTF" ' %s' "-t=${opt_tmp_dir}"

			# easytls-client-disconnect.sh
				"$EASYTLS_PRINTF" '%s\n' "'"
				"$EASYTLS_PRINTF" '%s' 'client-disconnect'
				"$EASYTLS_PRINTF" ' %s' "'"
				[ -n "${EASYTLS_FOR_WINDOWS}" ] && \
					"$EASYTLS_PRINTF" "%s " "$EASYTLS_SH_EXE"
				"$EASYTLS_PRINTF" '%s' "${EASYTLS_CLIDIS_SH} -v"
				"$EASYTLS_PRINTF" ' %s' "-s=${EASYTLS_CLIDIS_VARS}"
				"$EASYTLS_PRINTF" '%s\n' "'"
				}

		} > "$EASYTLS_SRVSCRIPT_CFG" || die "Failed to create config file"

		# Instructions temp file
		"$EASYTLS_PRINTF" '\n    %s %s\n' 'config' "$EASYTLS_SRVSCRIPT_CFG" > \
			"$EASYTLS_INSTRUCT_TEMP"

		# Linux instructions
		"$EASYTLS_PRINTF" '\n%s\n\n' '===================='
		"$EASYTLS_PRINTF" '%s\n\n' '* Configure OpenVPN to use easytls scripts'
		"$EASYTLS_PRINTF" '%s\n' '  Add this to your OpenVPN Server config file:'
		"$EASYTLS_CAT" "$EASYTLS_INSTRUCT_TEMP"
		"$EASYTLS_RM" "$EASYTLS_INSTRUCT_TEMP"

		"$EASYTLS_PRINTF" '\n%s\n' '* Easy-TLS Scripts configuration completed'
	;;
	*)	# Other value -> Quit
		"$EASYTLS_PRINTF" '\n%s\n' '  Quit!'
	esac

	# Do not update master hash - No changes have been made
	skip_master_hash=1
} # => interactive_scripts ()

# Inter-active self-sign menu
interactive_selfsign ()
{
	"$EASYTLS_PRINTF" '\n%s\n' 'Easy-TLS Inter-active self-sign certificate Menu.'

	# Capture the command line to inline
	cmd_line='inline'

	# Choose Key
	ia_question_help='
* Available Self-signed certificates:'
	ia_question_text='
  [1] Self-signed server
  [2] Self-signed client

  Select the type of self-sign certificate to build:'
	interactive_question

	case "${ia_answer}" in
	1)	# Self-signed server
		cert_type='Server'
		interactive_selfsign_CN
		svr_name="${common_name}"
		interactive_selfsign_PW
		build_self_sign "${cert_type}" "${svr_name}"
	;;
	2)	# Self-signed client
		cert_type='Client'
		interactive_selfsign_CN
		cli_name="${common_name}"
		interactive_selfsign_PW
		interactive_selfsign_peer
		EASYTLS_PEER_FPR="${common_name}"
		build_self_sign "${cert_type}" "${cli_name}"
	;;
	*)	# Other value -> Quit
		"$EASYTLS_PRINTF" '\n%s\n' '  Quit!'
		skip_master_hash=1
	esac
} # => interactive_selfsign ()

# Set commonName and verify this cert does not exist
interactive_selfsign_CN ()
{
	unset -v common_name
	ia_question_help='
* This field only requires the certificate commonName,
  it does not require the complete file name.'

	while :
	do
		ia_question_text="
  Enter the commonName of your * ${cert_type} * certificate:"
		interactive_question
		[ -n "${ia_answer}" ] || continue
		common_name="${ia_answer}"
		cert_file="${EASYTLS_PKI}/${common_name}.crt"
		[ -f "${cert_file}" ] || {
			cmd_line="${cmd_line} ${common_name}"
			break
			}
		ia_question_text="  Press enter to continue.."
		"${EASYTLS_PRINTF}" "\n%s\n\n" \
			"  A certificate named ${common_name} already exists!"
		interactive_wait_for_answer
	done
} # => interactive_selfsign_CN ()

# Set peer commonName and verify this cert does exist
interactive_selfsign_peer ()
{
	unset -v common_name
	ia_question_help='
* This field only requires the certificate commonName,
  it does not require the complete file name.'

	while :
	do
		ia_question_text="
  To share your client and server fingerprints,
  enter the commonName of your * Server * certificate:"
		interactive_question
		[ -n "${ia_answer}" ] || continue
		common_name="${ia_answer}"
		cert_file="${EASYTLS_PKI}/${common_name}.crt"
		[ -f "${cert_file}" ] && {
			cmd_line="${cmd_line} ${common_name}"
			export EASYTLS_PEER_FPR="${common_name}"
			break
			}
		ia_question_text="  Press enter to continue.."
		"${EASYTLS_PRINTF}" "\n%s\n\n" \
			"  A certificate named ${common_name} does not exist!"
		interactive_wait_for_answer
	done
} # => interactive_selfsign_peer ()

# Choose self-sign cert/key with/without password
interactive_selfsign_PW ()
{
	unset -v openssl_nodes
	default='N'
	ia_question_help="
* Do you want to use a password ?

  Leave this blank to use the default

  * Default: $default"

	ia_question_text='
  Enter (y)es or (n)o:'
	while :
	do
		interactive_question
		ia_answer="${ia_answer:-$default}"
		case "$ia_answer" in
		Y|y|Yes|yes|YES)
			EASYTLS_SS_PASSWORD=1
			break
		;;
		N|n|No|no|NO)
			break
		;;
		*)	: # Ignore
		esac
	done

	easytls_verbose
	#easytls_verbose "Config: $opt_disable_list"
} # => interactive_selfsign_PW ()

# Set commonName and verify cert purpose
interactive_common_name ()
{
	unset -v common_name
	ia_question_help='
* This field only requires the certificate commonName,
  it does not require the complete file name.'
	ia_question_text="
  Enter the commonName of your * ${cert_type} * certificate:"

	while :
	do
		interactive_question
		common_name="${ia_answer}"
		cert_file="${EASYRSA_PKI}/issued/${common_name}.crt"
		[ -n "${EASYTLS_NO_CA}" ] && cert_file="${EASYTLS_PKI}/${common_name}.crt"
		interactive_verify_cert && {
			cmd_line="${cmd_line} ${common_name}"
			break
			}
	done
} # => interactive_common_name ()

# Set TLS-Crypt-V2 Client Group key name
interactive_gkey_name ()
{
	unset -v opt_gkey_name
	ia_question_help='
* This field requires a UNIQUE name for this TLS-Crypt-V2 GROUP key.'
	ia_question_text="
  Enter the unique name for your GROUP key:"

	while :
	do
		interactive_question
		opt_gkey_name="${ia_answer}"
		[ -n "${opt_gkey_name}" ] || {
			interactive_try_again "This field CANNOT be empty"
			continue
			}
		#gkey_file="${EASYTLS_PKI}/${opt_gkey_name}.key"
		break
		#[ -f "${gkey_file}" ] || break
		#print "* This key file already exists: ${gkey_file}"
	done
} # => interactive_gkey_name ()

# Verify the certificate and purpose
interactive_verify_cert ()
{
	[ -f "$cert_file" ] || {
		"$EASYTLS_PRINTF" '\n%s\n' "  ERROR: Missiing certificate $cert_file"
		return 1
		}

	"$EASYTLS_GREP" -q "TLS Web $cert_type" "$cert_file" || {
		"$EASYTLS_PRINTF" '\n%s\n' "  ERROR: Certificate must be a $cert_type"
		return 1
		}
} # => interactive_verify_cert ()

# Set option --sub-key-name
interactive_sub_key_name ()
{
	unset -v sub_key_name
	default='NOSUBKEY'
	ia_question_help='
* Each X509 Client certificate can have multiple TLS-Crypt-V2 keys,
  these keys are referred to as Sub-keys.  Each Sub-key is used in
  a separate inline file with the same X509 Client certificate.'
	ia_question_text='
  Enter the Sub-key Name for your key or leave blank to continue:'
	interactive_question
	ia_answer="${ia_answer:-$default}"
	if [ "$ia_answer" != "$TLSKEY_SUBNAME" ]
	then
		sub_key_name="$ia_answer"
		cmd_line="--sub-key-name=${sub_key_name} ${cmd_line}"
		export TLSKEY_SUBNAME="${sub_key_name}"
	else
		# Otherwise this returns a hidden error
		:
	fi

	[ "$TLSKEY_SUBNAME" = 'NOSUBKEY' ] || {
		full_name="${common_name}-${TLSKEY_SUBNAME}"
		inline_file="${EASYTLS_PKI}/${full_name}.inline"
		key_file="${EASYTLS_PKI}/${full_name}-tls-crypt-v2.key"
		metadata_debug="${EASYTLS_META_DATA_D}/${full_name}-tls-crypt-v2.metadata"
		unset -v full_name
		}
} # => interactive_sub_key_name ()

# Set key direction
interactive_key_direction ()
{
	unset -v key_direction
	if [ "$cert_type" = "Server" ]
	then
		default=0
	else
		default=1
	fi
	ia_question_help="
* Select Key direction.   Default: Server=0 | Client=1

  * Currently selected default is: $default"
	ia_question_text='
  Enter 0 or 1, or leave blank for default:'
	while :
	do
		interactive_question
		ia_answer="${ia_answer:-$default}"
		case "$ia_answer" in
		0|1)
			key_direction="$ia_answer"
			break
		;;
		*)	: # Ignore
		esac
	done
	cmd_line="${cmd_line} $key_direction"
} # => interactive_key_direction ()

# Set option no-key
interactive_opt_no_key ()
{
	unset -v opt_no_key
	default='Y'
	ia_question_help="
* Do you have the private key for this X509 certificate ?

  * Default: $default"
	ia_question_text='
  Enter (y)es or (n)o:'
	while :
	do
		interactive_question
		ia_answer="${ia_answer:-$default}"
		case "$ia_answer" in
		Y|y|Yes|yes|YES)
			break
		;;
		N|n|No|no|NO)
			opt_no_key='no-key'
			cmd_line="${cmd_line} no-key"
			break
		;;
		*)	: # Ignore
		esac
	done
} # => interactive_opt_no_key ()

# Set option add-dh
interactive_opt_add_dh ()
{
	unset -v opt_add_dh
	default='Y'
	ia_question_help="
* Do you want to inline the Diffie-Hellman parameters file ?

  Default DH File: $default_dh_file

  * Default: $default"
	ia_question_text='
  Enter (y)es or (n)o:'
	while :
	do
		interactive_question
		ia_answer="${ia_answer:-$default}"
		case "$ia_answer" in
		Y|y|Yes|yes|YES)
			opt_add_dh='add-dh'
			cmd_line="${cmd_line} add-dh"
			break
		;;
		N|n|No|no|NO)
			break
		;;
		*)	: # Ignore
		esac
	done
} # => interactive_opt_add_dh ()

# Set custom dh file
interactive_custom_dh ()
{
	unset -v opt_custom_dh
	default='none'
	ia_question_help="
* No Diffie-Hellman parameters file was found!

  If you need to use a custom Diffie-Hellman parameters file then
  enter the file location and name.

  Otherwise, quit this menu and use Easy-RSA to create your
  Diffie-Hellman parameters file.

  If you leave this blank then the Diffie-Hellman parameters file
  will NOT be add to the inline-file.

  * Default: $default"
	ia_question_text='
  Enter the DH file name:'
	interactive_question
	ia_answer="${ia_answer:-$default}"
	[ "$ia_answer" = "$default" ] || {
		opt_add_dh='add-dh'
		opt_custom_dh="$ia_answer"
		cmd_line="--dh=\"${opt_custom_dh}\" ${cmd_line} add-dh"
		export EASYRSA_DH_FILE="${opt_custom_dh}"
		}
} # => interactive_custom_dh ()

# Set option no-md
interactive_opt_no_md ()
{
	unset -v opt_no_md
	default='Y'
	[ -n "${no_metadata}" ] && default='N'
	ia_question_help="
* Do you want to include the client metadata in the inline file ?

  The metadata does not contain any security sensitive data but
  you may prefer to omit it.

  * Default: $default"
	ia_question_text='
  Enter (y)es or (n)o:'
	while :
	do
		interactive_question
		ia_answer="${ia_answer:-$default}"
		case "$ia_answer" in
		Y|y|Yes|yes)
			break
		;;
		N|n|No|no)
			opt_no_md='no-md'
			cmd_line="${cmd_line} no-md"
			break
		;;
		*)	: # Ignore
		esac
	done
} # => interactive_opt_no_md ()

# Set option add-hw
interactive_opt_add_hw ()
{
	[ -n "$opt_no_md" ] && return 0
	unset -v opt_add_hw
	default='N'
	[ -n "${add_hardware}" ] && default='Y'
	ia_question_help="
* Do you want to include the hardware addresses in the client metadata ?

  * Default: $default"
	ia_question_text='
  Enter (y)es or (n)o:'
	while :
	do
		interactive_question
		ia_answer="${ia_answer:-$default}"
		case "$ia_answer" in
		Y|y|Yes|yes)
			opt_add_hw='add-hw'
			cmd_line="${cmd_line} add-hw"
			break
		;;
		N|n|No|no)
			break
		;;
		*)	: # Ignore
		esac
	done
} # => interactive_opt_add_hw ()

# Set hardware addresses
interactive_hwaddr ()
{
	opt_hardware=""
	ia_question_help='
* You can lock this key to specific filter-addresses.

  Hardware-addresses can be in the form of:
  * 0123456789ab or 01-23-45-67-89-AB or 01:23:45:67:89:AB

  IP-addresses can be in the form of:
  * IPv4 CIDR - eg: 1.2.0.0/16, 1.2.3.0/24 or 1.2.3.4/32
  * IPv6 CIDR - eg: 2000:1:2:3::/64 or 2000:1:2:3:4:5:6:7/128
  Ranges are in the first forms above. If you want to lock to a specific
  IP address (Not recommended) then you must use a host mask:
  * 1.2.3.4/32(v4) or 2000:1:2:3:4:5:6:7/128(v6)

  This field can contain any mixture of valid filter-addresses,
  however, each filter-address MUST be entered individually.'
	ia_question_text='
  Enter a single filter-address or leave blank to continue:'
	while :
	do
		interactive_question
		ia_question_help=''
		# EASYTLS_TLSCV2_HWLIST is set in verify stage so unset it
		# EASYTLS_TLSCV2_HWLIST will be recreated by the build routine
		[ -z "$ia_answer" ] && unset -v EASYTLS_TLSCV2_HWLIST && break

		# Verify valid HWADDR
		interactive_filter_address_check "$ia_answer" || return 1

		# Single address
		opt_hardware="$ia_answer"

		# Add this address to the list
		opt_hardware_list="$opt_hardware $opt_hardware_list"
		ia_question_text="Current list: $opt_hardware_list

  Enter a single filter-address or leave blank to continue:"
	done
	cmd_line="${cmd_line} ${opt_hardware}"
} # => interactive_hwaddr ()

# Check filter-adress
interactive_filter_address_check ()
{
	md_temp_addr="${1}"
	if hw_addr_hex_check "${md_temp_addr}"; then return 0;
	elif validate_ip4_data "${md_temp_addr}"; then return 0;
	elif validate_ip6_data "${md_temp_addr}"; then
		md_temp_addr="${valid_hextets}/${mask_len}"
		if expand_ip6_address "${md_temp_addr}"
		then
			# Returned: ${full_valid_hextets} ${full_subnet_addr6}"
			: # OK
		else
			die "Invalid Address: ${md_temp_addr}"
		fi
		unset -v valid_octets mask_len md_temp_addr
		EASYTLS_TLSCV2_HWLIST="${EASYTLS_TLSCV2_HWLIST} ${md_temp_addr}"
	else
		die "Invalid Address: '${md_temp_addr}'"
	fi
} # => interactive_filter_address_check ()

# Get Full path
get_full_path ()
{
	[ -n "${1}" ] || return 1
	part_path="${1}"
	if [ -n "${EASYTLS_FOR_WINDOWS}" ]
	then
		# Windows
		if [ "${part_path%%?:*}" = "" ]
		then
			# Full path
			:
		elif [ "${part_path%%/*}" = "." ]
		then
			# Relative path
			part_path="${EASYTLS_work_dir}/${part_path#./}"
		elif [ "${part_path%%/*}" = "" ]
		then
			# Naked path
			part_path="${EASYTLS_work_dir}/${part_path}"
		else
			# Unknown
			return 1
		fi
	else
		# *nix
		if [ "${part_path%%/*}" = "." ]
		then
			# Relative path
			part_path="${EASYTLS_work_dir}/${part_path#./}"
		elif [ "${part_path%%/*}" != "" ]
		then
			# Naked path
			part_path="${EASYTLS_work_dir}/${part_path}"
		else
			# Full path
			:
		fi
	fi
	"${EASYTLS_PRINTF}" "%s" "${part_path}"
} # => get_full_path ()

# Set CA path
interactive_ca_path ()
{
	unset -v opt_ca_path
	default="${EASYRSA_PKI}"
	ia_question_help="
* Enter the path to the location of your Easy-RSA PKI directory.

  Leave this blank to use the default Easy-RSA PKI path.

  * Default: ${default}"

	IA_key_index="easytls/data/easytls-key-index.txt"
	EASYRSA_ca_cert="ca.crt"

	while :
	do
		ia_question_text='
  Enter the path name:'
		interactive_question
		ans_ca_path="${ia_answer:-$default}"

		# Check for absolute path
		# Because the server does not run from the script directory
		ans_ca_path="$(get_full_path "${ans_ca_path}")"

		# check for ${ia_answer}/easytls/data/easytls-key-index.txt
		# because it is always used by easytls-verify
		if [ -f "${ans_ca_path}/${IA_key_index}" ]
		then
			if [ -n "${EASYTLS_NO_CA}" ]
			then
				break
			else
				# Normal PKI mode
				[ -f "${ans_ca_path}/${EASYRSA_ca_cert}" ] && break
				print "
    CA certificate does not exist:
    * ${ans_ca_path}/${EASYRSA_ca_cert}"
			fi
		else
			# easytls-key-index.txt must exist
			print "
    Easy-TLS Key Index does not exist:
    * ${ans_ca_path}/${IA_key_index}"
		fi

		ia_question_text="
      *** ERROR - Press ENTER to continue ***"
		interactive_wait_for_answer
	done

	# Set answer and return
	opt_ca_path="${ans_ca_path}"
	unset -v IA_key_index EASYRSA_ca_cert
} # => interactive_ca_path ()

# hash algorithm
interactive_hashalgo ()
{
	[ "${EASYTLS_HASH_ALGO}" = "SHA256" ] || \
		opt_hashalgo="--hash=${EASYTLS_HASH_ALGO}"
} # => interactive_hashalgo ()

# Set Custom Group
interactive_custom_group ()
{
	opt_customg=""
	default="$TLSKEY_CUSTOM_GRP"
	ia_question_help='
* Configure a custom group.

  You can configure a single Custom-Group like so:

    $ ./easytls config custom.group NAME

  If you want to configure a Custom-Group now then quit this menu.

  If you have configured your Custom-Group or do not require a Custom-Group
  then leave this field blank.

  * Your current Custom-Group is:'
		[ "$TLSKEY_CUSTOM_GRP" = "EASYTLS" ] || \
			ia_question_help="$ia_question_help $TLSKEY_CUSTOM_GRP"
		ia_question_text='
  Enter your Custom-Group or leave this blank to continue:'
		interactive_question
		opt_customg="${ia_answer:-$default}"

		if [ "$opt_customg" = "EASYTLS" ]
		then
			# Forget default
			unset -v opt_customg
		else
			# This will only show if user enters a Custom-Group
			# Otherwise, the Custom-Group is what-ever is configured
			cmd_line="--custom-group=${opt_customg} ${cmd_line}"
			export TLSKEY_CUSTOM_GRP="${opt_customg}"
		fi
	easytls_verbose "Config: $opt_customg"
} # => interactive_custom_group ()

# Set tlskey allowed age in days
interactive_key_age ()
{
	unset -v opt_key_age
	default="1825" # 5 Years
	ia_question_help="
* Enter the maximum allowable age in days of your TLS-Crypt-V2 Client keys.

  Enter 0 to disable this check.

  Leave this blank to use the default age. (1825 days is 5 years)

  * Default: $default"
	ia_question_text='
  Enter the allowable age:'
	interactive_question
	key_age_days="${ia_answer:-$default}"
	[ "$key_age_days" = "1825" ] || opt_key_age="-x=${key_age_days}"
	easytls_verbose "Config: $opt_key_age"
} # => interactive_key_age ()

# Set use of disabled-list
interactive_disabled_list ()
{
	unset -v opt_disable_list
	default='Y'
	ia_question_help="
* Do you want to use the dynamic disabled-list ?

  Leave this blank to use the default

  * Default: $default"

	ia_question_text='
  Enter (y)es or (n)o:'
	while :
	do
		interactive_question
		ia_answer="${ia_answer:-$default}"
		case "$ia_answer" in
		Y|y|Yes|yes|YES)
			break
		;;
		N|n|No|no|NO)
			opt_disable_list="-d"
			break
		;;
		*)	: # Ignore
		esac
	done

	easytls_verbose
	easytls_verbose "Config: $opt_disable_list"
} # => interactive_disabled_list ()

# Set the temp dir
interactive_tmp_dir ()
{
	unset -v opt_tmp_dir
	default="$EASYTLS_tmp_dir"
	ia_question_help="
* Enter the temporary directory path where the script can write temp files.

  * Default: $default"
	ia_question_text='
  Enter the path name:'

	# Verify tmp.dir is set
	[ -n "$EASYTLS_tmp_dir" ] || {
		error_msg "Server script tmp.dir is not configured."
		error_msg "To configure a temporary directory use:
"
		error_msg "  ./easytls config tmp.dir <directory>
"
		error_msg "Recommended setting for tmp.dir:
"
		if [ -n "${EASYTLS_FOR_WINDOWS}" ]
		then
			error_msg "  Windows - ${host_drv}/Windows/Temp"
		else
			error_msg "  *nix - /var/easytls (Create with sudo)"
			error_msg "  systemd - /tmp (Use openvpn-server@.service)"
		fi
		die "Missing setting tmp.dir"
		}

	while :
	do
		interactive_question
		ans_tmp_dir="${ia_answer:-$default}"

		# Check for absolute path
		# Because the server does not run from the script directory
		ans_tmp_dir="$(get_full_path "${ans_tmp_dir}")"

		# Verify tmp.dir exists
		[ -d "${ans_tmp_dir}" ] || {
			error_msg "Server script tmp.dir is not created."
			error_msg "Please create your chosen temporary directory"
			error_msg "Recommended setting:"
			if [ -n "${EASYTLS_FOR_WINDOWS}" ]
			then
				error_msg " Windows - ${host_drv}/Windows/Temp"
			else
				error_msg " *nix - /var/easytls (Create with sudo)"
				error_msg " systemd - /tmp"
			fi
			die "Missing directory: ${ans_tmp_dir}"
			}
		break
	done
	if [ -n "${EASYTLS_FOR_WINDOWS}" ]
	then
		[ "${ans_tmp_dir}" = "${host_drv}/Windows/Temp" ] || {
			opt_tmp_dir="${ans_tmp_dir}"
			easytls_verbose "Config: $opt_tmp_dir"
			}
	else
		[ "${ans_tmp_dir}" = "/tmp" ] || {
			opt_tmp_dir="${ans_tmp_dir}"
			easytls_verbose "Config: $opt_tmp_dir"
			}
	fi
} # => interactive_tmp_dir ()

# Set the X509 method
interactive_x509_method ()
{
	unset -v opt_x509_method
	default="0"
	ia_question_help='
* Select the X509 method to verify client certificates.

  The default setting will not invoke any X509 code in the script
  and only do TLS verification.  X509 is then handled by OpenVPN.'
	ia_question_text='
  [0] Do not use X509 for TLS key verification (default)
  [1] Verify via CRL:   Verify your cert. using OpenSSL crl
  [2] Verify via CA:    Verify your cert. using OpenSSL ca (DOES NOT WORK)
  [3] Verify via Index: Verify your cert. by searching your current CRL

  Select the X509 method to use or leave blank for default:'

	while :
	do
		interactive_question
		ia_answer="${ia_answer:-$default}"
		case "$ia_answer" in
		0)	# OFF
			break
		;;
		1)	# crl
			X509_METHOD=1
			opt_x509_method="--v1"
			break
		;;
		2)	# ca
			X509_METHOD=2
			opt_x509_method="--v2"
			break
		;;
		3)	# index
			X509_METHOD=3
			opt_x509_method="--v3"
			break
		;;
		'')	# default: off
			break
		;;
		*)	: # ERROR: Try again
		esac
	done
	easytls_verbose "Config: $opt_x509_method"

	[ -n "${opt_x509_method}" ] || return 0

	unset -v opt_load_ca_id
	unset -v opt_preload_id
	default="off"
	ia_question_help='
* Select how the verify script loads the CA Identity.

  The default setting is to build the CA-ID in the script (SLOWEST)'
	ia_question_text='
  [1] --cache-id:   Load the CA-ID from easytls-ca-identity.txt (MEDIUM)
  [2] --preload-id: Load the CD-ID as a string with the script (FASTEST)

  Select the loading method to use or leave blank for default:'

	while :
	do
		interactive_question
		case "${ia_answer}" in
		1)	# cache
			opt_load_ca_id="-a"
			break
		;;
		2)	# preload
			opt_preload_id="-p="
			break
		;;
		'')	# default: off
			break
		;;
		*)	: # ERROR: Try again
		esac
	done

	if [ -n "${opt_preload_id}" ]
	then
		[ -f "$EASYTLS_CA_IDENTITY" ] || \
			die "Cannot find your CA-ID file, init-tls first."

		ia_question_help='
* Enter your CD-ID.

  Leave this blank to use the default Easy-TLS CA-ID.'
		ia_question_text='
  Enter the CA-ID:'

		while :
		do
			interactive_question
			if [ -n "$ia_answer" ]
			then
				opt_preload_id="$ia_answer"
				"$EASYTLS_PRINTF" '%s' "$opt_preload_id" | \
					"$EASYTLS_GREP" -q '^[[:xdigit:]]\{40\}$' || {
						error_msg "Invalid CA-ID"
						continue
						}
				break
			else
				opt_preload_id="$EASYTLS_MASTER_ID"
				break
			fi
		done
		opt_preload_id="-p=${opt_preload_id}"
	fi

	[ -n "${opt_load_ca_id}" ] && easytls_verbose "Config: $opt_load_ca_id"
	[ -n "${opt_preload_id}" ] && easytls_verbose "Config: $opt_preload_id"
} # => interactive_x509_method ()

# Set use client connect srcipt
interactive_client_connect ()
{
	unset -v opt_client_connect
	default='Y'
	ia_question_help="
* Do you want to use the Easy-TLS client-connect script ?

  Leave this blank to use the default

  * Default: $default"
	ia_question_text='
  Enter (y)es or (n)o:'
	while :
	do
		interactive_question
		ia_answer="${ia_answer:-$default}"
		case "$ia_answer" in
		Y|y|Yes|yes|YES)
			opt_client_connect=1
			break
		;;
		N|n|No|no|NO)
			break
		;;
		*)	: # Ignore
		esac
	done

	easytls_verbose
	easytls_verbose "Config: $opt_hw_required"
} # => interactive_client_connect ()


# Openvpn --script-security
interactive_script_security ()
{
	unset -v opt_script_security
	default='N'
	ia_question_help="
* Do you want to use user/password authentication ?

  Leave this blank to use the default

  * Default: $default"
	ia_question_text='
  Enter (y)es or (n)o:'
	while :
	do
		interactive_question
		ia_answer="${ia_answer:-$default}"
		case "$ia_answer" in
		Y|y|Yes|yes|YES)
			opt_script_security=3
			break
		;;
		N|n|No|no|NO)
			opt_script_security=2
			break
		;;
		*)	: # Ignore
		esac
	done

	easytls_verbose
	#easytls_verbose "Config: $opt_script_security"
} # => interactive_script_security ()

# Set use of kill-client mode
interactive_kill_client ()
{
	unset -v opt_kill_client
	default='Y'
	ia_question_help="
* Do you want to use the Easy-TLS client-connect script
  to force a client which fails authorisation to shut-down ?

  Leave this blank to use the default

  * Default: $default"
	ia_question_text='
  Enter (y)es or (n)o:'
	while :
	do
		interactive_question
		ia_answer="${ia_answer:-$default}"
		case "$ia_answer" in
		Y|y|Yes|yes|YES)
			opt_kill_client='-k'
			break
		;;
		N|n|No|no|NO)
			break
		;;
		*)	: # Ignore
		esac
	done

	easytls_verbose
	easytls_verbose "Config: $opt_kill_client"
} # => interactive_kill_client ()

# Check for IP filter
interactive_ip_filter ()
{
	unset -v opt_ip_filter
	default='N'
	ia_question_help="
* Do you want to use client source-IP metadata filter ?

  Leave this blank to use the default

  * Default: $default"
	ia_question_text='
  Enter (y)es or (n)o:'
	while :
	do
		interactive_question
		ia_answer="${ia_answer:-$default}"
		case "$ia_answer" in
		Y|y|Yes|yes|YES)
			opt_ip_filter=1
			break
		;;
		N|n|No|no|NO)
			break
		;;
		*)	: # Ignore
		esac
	done

	easytls_verbose
	#easytls_verbose "Config: $opt_script_security"
} # => interactive_ip_filter ()

# Set use of hardware-address required
interactive_hw_required ()
{
	unset -v opt_hw_required
	default='1'
	ia_question_help="
* Select the level of hardware-address verification required ?
+----------------------------------------
| TLS-Auth/Crypt and TLS-Crypt-V2
+----------------------------------------
| [0] Lowest - Allow all valid TLS-AUTH/Crypt/V2 keys to connect.
|     ALL TLS-Crypt-V2 key extended tests are NOT peformed.
| [1] Low - Functionally equivalent to [0] Low - Allow all..
|     Except, ALL TLS-Crypt-V2 key extended tests are peformed.
|     Same as default [2], except hwaddr-mismatches are IGNORED.
| [2] Default - Do not require clients to push a hwaddr.
|     TLS-Crypt-V2 keys with a hwaddr mismatch will be disconnected.
|     TLS-Crypt-V2 keys without a hwaddr can connect.
|     TLS Auth and Crypt-v1 keys can connect.
| [3] Medium - Require all clients to push a hwaddr.
|     TLS-Crypt-V2 keys with a hwaddr mismatch will be disconnected.
|     TLS-Crypt-V2 keys without a hwaddr can connect but must push a hwaddr.
|     TLS Auth and Crypt-v1 keys can connect but must push a hwaddr.
+----------------------------------------
| TLS-Crypt-V2 ONLY
+----------------------------------------
|| [4] Medium-High - Do not require clients to push a hwaddr.
||     TLS-Crypt-V2 keys without a Hardware-address can connect.
|| [5] High - Require all clients to push a hwaddr.
||     TLS-Crypt-v2 keys without a hwaddr can connect but must push a hwaddr.
|| [6] Very-High - hwaddr verification is enforced on all clients.
||     TLS-Crypt-V2 key must have a hwaddr and client must push a hwaddr.

   Leave this blank to use the default

 * Default: $default"
	ia_question_text='
  Enter your required level:'
	while :
	do
		interactive_question
		ia_answer="${ia_answer:-$default}"
		case "$ia_answer" in
		0)
			opt_hw_required='-a'
			break
		;;
		1)
			opt_hw_required='-m'
			break
		;;
		2)
			break
		;;
		3)
			opt_hw_required='-p'
			break
		;;
		4)
			opt_hw_required='-c'
			break
		;;
		5)
			opt_hw_required='-p -c'
			break
		;;
		6)
			opt_hw_required='-k'
			break
		;;
		*)	: # Ignore
		esac
	done

	easytls_verbose
	easytls_verbose "Config: $opt_hw_required"
} # => interactive_hw_required ()

# Search patterns to remove from inline-metadata
interactive_metadata_pattern ()
{
	opt_pattern=""
	ia_question_help='
* Search works by single contiguous word match per iteration.

  After entering a search pattern then enter a blank pattern to continue.

  Using no seach pattern at all will erase all metadata from the inline-file.'

	while :
	do
		ia_question_text="
  Current search patten: ${opt_pattern}

  Enter a single search pattern or leave blank to continue:"
		interactive_question
		[ -z "$ia_answer" ] && break

		# Add this HWADDR to the list
		opt_pattern="${ia_answer} ${opt_pattern}"
	done
	cmd_line="${cmd_line} ${opt_pattern}"
} # => interactive_metadata_pattern ()

# Automatically detect no-ca mode
automatic_no_ca ()
{
	opt_no_ca='-z'
	[ -n "${EASYTLS_NO_CA}" ] || unset -v opt_no_ca
} # => automatic_no_ca ()



############################################################################
#
# EASYTLS master a.k.a. wiscii hash Section
#

# Generate list of files to hash
# Use newline to delimit names to ignore spaces when read
generate_master_list () {
	# If TLS has not been initialised then bail-out
	[ -f "${EASYTLS_CONFIG_FILE}" ] || return 0

	# file hashing is disabled
	[ -z "${FILE_HASH_DISABLED}" ] || return 0

	# Initialise inline-list to the inline index file
	inline_file_list="${EASYTLS_INLINE_INDEX}"

	# Hash all inline-files
	# shellcheck disable=SC2034 # Appear unused
	while read -r \
		saved_inline_hash inline_serial_UNUSED CN sub_key tlskey_serial_UNUSED
	do
		# Skip comment
		[ -n "${saved_inline_hash%%#*}" ] || continue

		# Set inline_file full name
		if [ "${sub_key}" = 'NOSUBKEY' ];then
			inline_file="${EASYTLS_PKI}/${CN}.inline"
		else
			inline_file="${EASYTLS_PKI}/${CN}-${sub_key}.inline"
		fi

		# Ignore missing (presumed deleted) files
		[ -f "${inline_file}" ] || continue

		# Add inline-file to inline-list, delimit with newline not space
		inline_file_list="${inline_file_list}
${inline_file}"
	done < "${EASYTLS_INLINE_INDEX}"

	unset -v inline_file  \
		saved_inline_hash inline_serial_UNUSED CN sub_key tlskey_serial_UNUSED

	# Initialise tlskey-list to the tlskey index file
	tlskey_file_list="${EASYTLS_TLSKEY_INDEX}"

	# Hash all tlskey_file
	# tls-auth / tls-crypt
	for tlskey_file in \
		"${EASYTLS_PKI}/tls-auth.key" "${EASYTLS_PKI}/tls-crypt.key"
	do
		# If the file exists, add it to the tlskey-list, delimit with newline
		[ -f "${tlskey_file}" ] || continue
		tlskey_file_list="${tlskey_file_list}
${tlskey_file}"
	done

	# tls-crypt-v2
	# shellcheck disable=SC2034
	while read -r tlskey_serial cert_serial_UNUSED CN sub_key
	do
		# Skip comment
		[ -n "${tlskey_serial%%#*}" ] || continue

		# Set tlskey_file full name
		if [ "${sub_key}" = 'NOSUBKEY' ];then
			tlskey_file="${EASYTLS_PKI}/${CN}-tls-crypt-v2.key"
			#key_metadata="${EASYTLS_META_DATA_D}/${CN}-tls-crypt-v2.metadata"
		else
			tlskey_file="${EASYTLS_PKI}/${CN}-${sub_key}-tls-crypt-v2.key"
			#key_metadata="${EASYTLS_META_DATA_D}/${CN}-${sub_key}-tls-crypt-v2.metadata"
		fi

		# Ignore missing (presumed deleted) files
		[ -f "${tlskey_file}" ] || continue

#		[ ! -f "${key_metadata}" ] || key_metadata_list="${key_metadata}
#${key_metadata_list}"

		# Add tlskey file to the list, delimit with newline
		tlskey_file_list="${tlskey_file_list}
${tlskey_file}"
	done < "${EASYTLS_TLSKEY_INDEX}"

	unset -v tlskey_file \
		generated_valid_hash tlskey_serial cert_serial_UNUSED CN sub_key

	# Add config-file and disabled-list and hash files to the UTIL list
	util_file_list="${EASYTLS_CONFIG_FILE}
${EASYTLS_DISABLED_LIST}"
} # => generate_master_list ()

# Master hash
generate_master_hash () {
	# Respect no file hash mode
	if [ -n "${FILE_HASH_DISABLED}" ]; then
		generated_master_hash="${fixed_hash}"
		return 0
	fi

	# Make sure to get a hash
	unset -v request_fixed_hash

	# Initialise the list variables
	unset -v inline_file_list tlskey_file_list util_file_list \
		key_metadata_list

	# Generate the lists above
	generate_master_list || die "generate_master_hash - generate_master_list"

	# Check for empty list - Required for FILE_HASH_DISABLED mode
	# Otherwise, an empty list is piped to SSL and it blocks
	if [ -z "${inline_file_list}${tlskey_file_list}${util_file_list}" ]; then
		generated_master_hash="${fixed_hash}"
		return 0
	fi

	# Use ssl unlock
	#master_hash_only=1

	# This does not appear to be any faster than cat.
	# Hashing a hash .. I don't think it matters here.
	# Choose hash
	#new_faster_hash || die "new_faster_hash"
	old_faster_hash || die "old_faster_hash"

	generated_master_hash="${generated_faster_hash%% *}"
	unset -v inline_file_list tlskey_file_list util_file_list \
		generated_faster_hash
} # => generate_master_hash ()

# new_faster_hash way
new_faster_hash () {
	# DISABLED_DELETE
	# Removed from from subshell but not required due to FILE_HASH_DISABLED
	#if [ -n "${request_fixed_hash}" ]; then
	#	generated_faster_hash="${fixed_hash}"
	#	return 0
	#fi

	# Generate a single hash of all the files via ssl
	old_IFS="$IFS"
	IFS="${new_line}"
	set --

	# List inline files
	for f in ${inline_file_list}; do set -- "$@" "${f}"; done
	# List tlskey files
	for f in ${tlskey_file_list}; do set -- "$@" "${f}"; done
	# List utility files
	for f in ${util_file_list}; do set -- "$@" "${f}"; done
	#if [ -z "${EASYTLS_FOR_WINDOWS}" ]; then
	#	# List metadata files
	#	for f in ${key_metadata_list}; do set -- "$@" "${f}"; done
	#fi

	# hash each file in the @ list to a single hash-list
	# hash the list-hash and return a single hash
	#print "FILE NAMES:"
	#printf '%s\n' "$@"
	#print "HASH LIST:"
	#printf '%s\n' "$(
	#	ssl_generate_new_master_files_hash "$@"
	#	)"

	hash_list_hash="$(
		master_hash_only=1
			ssl_generate_new_master_files_hash "$@" | \
				ssl_generate_old_master_data_hash
		)" || \
			die "new_faster_hash - hash_list_hash"
	# Use hash
	generated_faster_hash="${hash_list_hash}"

	set --
	IFS="${old_IFS}"
	unset -v old_IFS hash_list_hash
} # => new_faster_hash ()

# old_faster_hash way
old_faster_hash () {
	# DISABLED_DELETE
	# Removed from from subshell but not required due to FILE_HASH_DISABLED
	#if [ -n "${request_fixed_hash}" ]; then
	#	generated_faster_hash="${fixed_hash}"
	#	return 0
	#fi

	# Generate a single hash of all the files via cat
	unset -v old_IFS
	[ -z "${IFS}" ] || old_IFS="$IFS"
	IFS="${new_line}"
	set --

	# List inline files
	for f in ${inline_file_list}; do set -- "$@" "${f}"; done
	# List tlskey files
	for f in ${tlskey_file_list}; do set -- "$@" "${f}"; done
	# List utility files
	for f in ${util_file_list}; do set -- "$@" "${f}"; done
	#if [ -z "${EASYTLS_FOR_WINDOWS}" ]; then
	#	# List metadata files
	#	for f in ${key_metadata_list}; do set -- "$@" "${f}"; done
	#fi

	# cat the list
	# pipe to SSL
	cat_list_hash="$(
		master_hash_only=1
			"${EASYTLS_CAT}" "$@" | ssl_generate_old_master_data_hash
		)" || \
			die "generate_master_hash - cat_list_hash"
	# Use hash
	generated_faster_hash="${cat_list_hash}"

	set --
	unset -v IFS
	[ -z "${old_IFS}" ] || IFS="${old_IFS}"
	unset -v old_IFS cat_list_hash
} # => old_faster_hash ()

# Save Master hash
save_master_hash () {
	# To update Master hash update_master_hash MUST be set
	[ -n "${update_master_hash}" ] || \
		die "save_master_hash - Missing: update_master_hash"
	[ -z "${master_save_hash_block}" ] || \
		die "Master save hash must only run once"

	generate_master_hash || die "save_master_hash - generate_master_hash"
	# If fixed hash has been generated then return ok
	if [ "${generated_master_hash}" = "${fixed_hash}" ]; then
		unset -v update_master_hash
		master_save_hash_block=1
		return 0
	fi

	validate_hash_block="$(( validate_hash_block - 1 ))"
	validate_hash "${generated_master_hash}" || \
		die "save_master_hash - validate_hash ${generated_master_hash}"
	"${EASYTLS_CP}" -f "${EASYTLS_FASTER_HASH}" "${EASYTLS_FASTER_HASH}-old"
	if save_file_hash "${EASYTLS_FASTER_HASH}" "${generated_master_hash}"
	then
		easytls_verbose "save_master_hash OK"
		unset -v update_master_hash
		[ -n "${return_hashes}" ] || unset -v generated_master_hash
		"${EASYTLS_RM}" -f "${EASYTLS_FASTER_HASH}-old"
		master_save_hash_block=1
		return 0
	fi
	error_msg "save_master_hash - save_file_hash"
	print "EASYTLS_PKI: ${EASYTLS_PKI}"
	print "EASYTLS_FASTER_HASH: ${EASYTLS_FASTER_HASH}"
	"${EASYTLS_RM}" -f "${EASYTLS_FASTER_HASH}-failed"
	"${EASYTLS_CP}" -f "${EASYTLS_FASTER_HASH}" "${EASYTLS_FASTER_HASH}-failed"
	return 1
} # => save_master_hash ()

# Verify Master hash
verify_master_hash () {
	[ -z "${master_verify_hash_block}" ] || \
		die "Master verify hash must only run once"
	read_hash_file "${EASYTLS_FASTER_HASH}" || {
		error_msg "verify_master_hash - read_hash_file"
		return 1
		}
	# Use hash
	saved_master_hash="${saved_file_hash}"

	validate_hash_block="$(( validate_hash_block - 1 ))"
	generate_master_hash || die "verify_master_hash - generate_master_hash"
	if match_two_hashes "${generated_master_hash}" "${saved_master_hash}"
	then
		easytls_verbose "verify_master_hash OK"
		master_verify_hash_block=1
		[ -n "${return_hashes}" ] || \
			unset -v saved_master_hash generated_master_hash saved_file_hash
		return 0
	fi
	error_msg "verify_master_hash - match_two_hashes"
	print "EASYTLS_PKI: ${EASYTLS_PKI}"
	print "EASYTLS_FASTER_HASH: ${EASYTLS_FASTER_HASH}"
	print "gen'd:${generated_master_hash} <==> saved:${saved_master_hash}"
	print "TIP: Use './easytls rehash' to correct this hash."
	return 1
} # => verify_master_hash ()



############################################################################
#
# EASYTLS shellcheck Section
#

# Fake function to assign variable values for SC2154
# Todo: Replace set_var() with ="${x:-y}"
shellcheck_ignore_2154 () {
	die "Do not use this function"
	EASYRSA_OPENSSL=
	EASYRSA_INDEX=
	EASYRSA_RAND_SN=

	EASYTLS_OPENVPN=

	EASYTLS=
	EASYTLS_PKI=
	EASYTLS_META_DATA_D=
	EASYTLS_DATA_DIR=
	EASYTLS_FASTER_HASH=
	EASYTLS_CONFIG_FILE=
	EASYTLS_CONFIG_HASH=
	EASYTLS_INLINE_INDEX=
	EASYTLS_INLINE_X_HASH=
	EASYTLS_TLSKEY_INDEX=
	EASYTLS_KEY_X_HASH=
	EASYTLS_DISABLED_LIST=
	EASYTLS_DISABLED_HASH=

	EASYTLS_TEMP_UPDATE=
	EASYTLS_TEMP_LIST=
	EASYTLS_TEMP_RECORD=
	EASYTLS_TEMP_LOCK=
	EASYTLS_TEMP_DELETED=

	EASYTLS_CA_IDENTITY=
	TLSKEY_SUBNAME=

	EASYTLS_VERIFY_V2_SH=
	EASYTLS_VERIFY_V2_VARS=
	#EASYTLS_VERIFY_V2_VEXM=

	EASYTLS_CLICON_SH=
	EASYTLS_CLICON_VARS=
	#EASYTLS_CLICON_VEXM=

	EASYTLS_CLIDIS_SH=
	EASYTLS_CLIDIS_VARS=
	#EASYTLS_CLIDIS_VEXM=

	EASYTLS_SRVSCRIPT_CFG=
	EASYTLS_INSTRUCT_TEMP=
	#EASYTLS_SRVSCRIPT_VEXM=

	EASYTLS_SS_AGE=
	EASYTLS_EC_CURVE=
	EASYTLS_ECPARAM_TMP=
} # => shellcheck_ignore_2154 ()

############################################################################
#
# EASYTLS SETUP Section
#

# Ensure required external binaries can be found
external_deps () {
	# Working directory
	EASYTLS_work_dir="${PWD}"
	delimiter='
'
	new_line='
'
	# MUST be unset
	unset unlock_ssl

	# Testing
	validate_hash_block=0
	isfp_count=0
	EASYTLS_STATUS=0

	# Identify Windows
	# shellcheck disable=SC2016
	EASYRSA_KSH='@(#)MIRBSD KSH R39-w32-beta14 $Date: 2013/06/28 21:28:57 $'
	# shellcheck disable=SC2154
	if [ "${KSH_VERSION}" = "${EASYRSA_KSH}" ]; then
		EASYTLS_FOR_WINDOWS=1
	fi

	# use Easy RSA
	set_var EASYRSA_OPENSSL			"openssl"

	# Use Easy TLS
	set_var EASYTLS_OPENVPN			"openvpn"

	# Set the external binary names
	EASYTLS_AWK='awk' # Checked by Easy-RSA3
	EASYTLS_CAT='cat' # Checked by Easy-RSA3
	EASYTLS_CP='cp' # Checked by Easy-RSA3
	EASYTLS_DATE='date'
	EASYTLS_GREP='grep'
	EASYTLS_LS='ls' # NOT Checked by Easy-RSA3
	EASYTLS_MKDIR='mkdir' # Checked by Easy-RSA3
	#EASYTLS_MKTEMP='mktemp' # Not used in Easy-TLS
	EASYTLS_MV='mv' # NOT Checked by Easy-RSA3
	EASYTLS_PRINTF='printf' # Checked by Easy-RSA3
	EASYTLS_RM='rm' # Checked by Easy-RSA3
	EASYTLS_SED='sed' # NOT Checked by Easy-RSA3

	# Directories and files
	if [ -n "${EASYTLS_FOR_WINDOWS}" ]; then
		# Windows
		# echo is provided by win
		host_drv="${PATH%%\:*}"
		base_dir="${EASYTLS_base_dir:-${host_drv}:/Progra~1/Openvpn}"
		EASYTLS_ersabin_dir="${EASYTLS_ersabin_dir:-${base_dir}/easy-rsa/bin}"
		EASYTLS_ovpnbin_dir="${EASYTLS_ovpnbin_dir:-${base_dir}/bin}"

		setup_err=0
		[ -d "${EASYTLS_ersabin_dir}" ] || {
			echo "Missing: ${EASYTLS_ersabin_dir}"
			setup_err=1
			}
		[ -d "${EASYTLS_ersabin_dir}" ] || {
			echo "Missing: ${EASYTLS_ersabin_dir}"
			setup_err=1
			}
		[ -d "${EASYTLS_ovpnbin_dir}" ] || {
			echo "Missing: ${EASYTLS_ovpnbin_dir}"
			setup_err=1
			}

		# Note: EASYRSA_OPENSSL not EasyTLS
		[ -f "${EASYTLS_ovpnbin_dir}/${EASYRSA_OPENSSL}.exe" ] || {
			echo "Missing: ${EASYTLS_ovpnbin_dir}/${EASYRSA_OPENSSL}.exe"
			setup_err=1
			}
		[ -f "${EASYTLS_ovpnbin_dir}/${EASYTLS_OPENVPN}.exe" ] || {
			echo "Missing: ${EASYTLS_ovpnbin_dir}/${EASYTLS_OPENVPN}.exe"
			setup_err=1
			}
		[ -f "${EASYTLS_ersabin_dir}/${EASYTLS_CAT}.exe" ] || {
			echo "Missing: ${EASYTLS_ersabin_dir}/${EASYTLS_CAT}.exe"
			setup_err=1
			}
		[ -f "${EASYTLS_ersabin_dir}/${EASYTLS_DATE}.exe" ] || {
			echo "Missing: ${EASYTLS_ersabin_dir}/${EASYTLS_DATE}.exe"
			setup_err=1
			}
		[ -f "${EASYTLS_ersabin_dir}/${EASYTLS_GREP}.exe" ] || {
			echo "Missing: ${EASYTLS_ersabin_dir}/${EASYTLS_GREP}.exe"
			setup_err=1
			}
		[ -f "${EASYTLS_ersabin_dir}/${EASYTLS_LS}.exe" ] || {
			echo "Missing: ${EASYTLS_ersabin_dir}/${EASYTLS_LS}.exe"
			setup_err=1
			}
		[ -f "${EASYTLS_ersabin_dir}/${EASYTLS_MV}.exe" ] || {
			echo "Missing: ${EASYTLS_ersabin_dir}/${EASYTLS_MV}.exe"
			setup_err=1
			}
		[ -f "${EASYTLS_ersabin_dir}/${EASYTLS_SED}.exe" ] || {
			echo "Missing: ${EASYTLS_ersabin_dir}/${EASYTLS_SED}.exe"
			setup_err=1
			}
		[ -f "${EASYTLS_ersabin_dir}/${EASYTLS_PRINTF}.exe" ] || {
			echo "Missing: ${EASYTLS_ersabin_dir}/${EASYTLS_PRINTF}.exe"
			setup_err=1
			}
		[ "${setup_err}" -eq 0 ] || {
			echo "Windows file setup error!"
			exit 9
			}
		unset -v setup_err

		export PATH="${EASYTLS_ersabin_dir};${EASYTLS_ovpnbin_dir};${PATH}"
		EASYTLS_SH_EXE="${EASYTLS_ersabin_dir}/sh.exe"
	fi

	# "Work_dir" needs improvement
	EASYTLS_WORK_DIR="${EASYTLS_work_dir}"
	# Source metadata lib
	lib_file="${EASYTLS_WORK_DIR}/easytls-metadata.lib"
	[ -f "${lib_file}" ] || \
		lib_file="${EASYTLS_WORK_DIR}/dev/easytls-metadata.lib"
	if [ -f "${lib_file}" ]; then
		# shellcheck source=dev/easytls-metadata.lib
		. "${lib_file}" || die "Failed to source: ${lib_file}"
		easytls_metadata_lib_ver
	fi

	# Source tctip lib
	lib_file="${EASYTLS_WORK_DIR}/easytls-tctip.lib"
	[ -f "${lib_file}" ] || \
		lib_file="${EASYTLS_WORK_DIR}/dev/easytls-tctip.lib"
	if [ -f "${lib_file}" ]; then
		# shellcheck source=dev/easytls-tctip.lib
		. "${lib_file}" || die "Failed to source: ${lib_file}"
		easytls_tctip_lib_ver
	fi
	unset -v lib_file EASYTLS_WORK_DIR
} # => external_deps ()

# vars setup
# Here sourcing of 'vars' if present occurs. If not present, defaults are used
# to support running without a sourced config format
vars_setup() {
	# Try to locate a 'vars' file in order of location preference.
	# If one is found, source it
	vars=

	# set up program path
	prog_file="$0"
	prog_dir="${prog_file%/*}"
	prog_vars="${prog_dir}/vars"
	# set up PKI path
	pki_vars="${EASYRSA_PKI:-${PWD}/pki}/vars"

	# command-line path:
	if [ -n "$EASYRSA_VARS_FILE" ]; then
		if [ ! -f "$EASYRSA_VARS_FILE" ]; then
			# If the --vars option does not point to a file
			# then show helpful error.
			die "The file '$EASYRSA_VARS_FILE' was not found."
		fi
		vars="$EASYRSA_VARS_FILE"
	# PKI location, if present:
	elif [ -f "$pki_vars" ]; then
		vars="$pki_vars"
	# EASYRSA, if defined:
	elif [ -n "$EASYRSA" ] && [ -f "$EASYRSA/vars" ]; then
		vars="$EASYRSA/vars"
	# program location:
	elif [ -f "$prog_vars" ]; then
		vars="$prog_vars"
	# vars of last resort ./vars
	elif [ -f "./vars" ]; then
		vars="./vars"
		warn "'./vars' of last resort !"
	fi

	# If a vars file was located, source it
	# If EASYRSA_NO_VARS is defined (not blank) this is skipped
	if [ -z "$EASYRSA_NO_VARS" ] && [ -n "$vars" ]; then
		# shellcheck disable=SC2034
		EASYRSA_CALLER=1
		# shellcheck disable=SC1090
		. "$vars"
		# Make this a verbose only notice
		easytls_verbose "Note: using Easy-RSA configuration from: $vars"
	fi

	# Set defaults, preferring existing env-vars if present
	set_var
	set_var EASYRSA					"$PWD"
	set_var EASYRSA_PKI				"${EASYRSA}/pki"
	set_var EASYRSA_TEMP_DIR		"$EASYRSA_PKI"
	set_var EASYRSA_INDEX			"$EASYRSA_PKI/index.txt"
	set_var EASYRSA_RAND_SN			"yes"

	set_var EASYTLS					"${PWD}"
	set_var EASYTLS_PKI				"${EASYRSA_PKI}/easytls"
	set_var EASYTLS_META_DATA_D		"${EASYTLS_PKI}/metadata"
	set_var EASYTLS_DATA_DIR		"${EASYTLS_PKI}/data"
	set_var EASYTLS_FASTER_HASH		"${EASYTLS_DATA_DIR}/easytls-faster.hash"
	set_var EASYTLS_CONFIG_FILE		"${EASYTLS_DATA_DIR}/easytls-config.txt"
	set_var EASYTLS_CONFIG_HASH		"${EASYTLS_DATA_DIR}/easytls-config.hash"
	set_var EASYTLS_INLINE_INDEX	"${EASYTLS_DATA_DIR}/easytls-inline-index.txt"
	set_var EASYTLS_INLINE_X_HASH \
		"${EASYTLS_DATA_DIR}/easytls-inline-index.hash"
	set_var EASYTLS_TLSKEY_INDEX	"${EASYTLS_DATA_DIR}/easytls-key-index.txt"
	set_var EASYTLS_KEY_X_HASH		"${EASYTLS_DATA_DIR}/easytls-key-index.hash"
	set_var EASYTLS_DISABLED_LIST \
		"${EASYTLS_DATA_DIR}/easytls-disabled-list.txt"
	set_var EASYTLS_DISABLED_HASH \
		"${EASYTLS_DATA_DIR}/easytls-disabled-list.hash"

	set_var EASYTLS_TEMP_UPDATE		"${EASYTLS_DATA_DIR}/easytls-temp.update"
	set_var EASYTLS_TEMP_LIST		"${EASYTLS_DATA_DIR}/easytls-temp.list"
	set_var EASYTLS_TEMP_RECORD		"${EASYTLS_DATA_DIR}/easytls-temp.record"
	set_var EASYTLS_TEMP_LOCK		"${EASYTLS_DATA_DIR}/easytls-temp.lock.d"
	set_var EASYTLS_TEMP_DELETED		"${EASYTLS_DATA_DIR}/easytls-temp.deleted"

	set_var EASYTLS_CA_IDENTITY		"${EASYTLS_DATA_DIR}/easytls-ca-identity.txt"
	set_var TLSKEY_SUBNAME			"NOSUBKEY"

	set_var EASYTLS_VERIFY_V2_SH	"${EASYTLS}/easytls-cryptv2-verify.sh"
	set_var EASYTLS_VERIFY_V2_VARS	"${EASYTLS}/easytls-cryptv2-verify.vars"

	set_var EASYTLS_CLICON_SH		"${EASYTLS}/easytls-client-connect.sh"
	set_var EASYTLS_CLICON_VARS		"${EASYTLS}/easytls-client-connect.vars"

	set_var EASYTLS_CLIDIS_SH		"${EASYTLS}/easytls-client-disconnect.sh"
	set_var EASYTLS_CLIDIS_VARS		"${EASYTLS}/easytls-client-disconnect.vars"

	set_var EASYTLS_SRVSCRIPT_CFG   "${EASYTLS}/easytls-script.conf"
	set_var EASYTLS_INSTRUCT_TEMP   "${EASYTLS}/easytls-script.help"

	set_var EASYTLS_SS_AGE			"3650"
	set_var EASYTLS_EC_CURVE		"secp384r1"
	set_var EASYTLS_ECPARAM_TMP		"${EASYTLS_DATA_DIR}/ecp.tmp"
} # vars_setup()

# variable assignment by indirection when undefined; merely exports
# the variable when it is already defined (even if currently null)
# Sets $1 as the value contained in $2 and exports (may be blank)
set_var() {
	[ "$#" -eq 0 ] && return
	var=$1
	shift
	value="$*"
	eval "export $var=\"\${$var-$value}\""
} #=> set_var()



############################################################################
#
# EASYTLS COMMAND Section
#

# Top level function
main () {
	EASYTLS_VERSION="2.8.0"

	# Be secure with a restrictive umask
	[ -n "$EASYTLS_NO_UMASK" ] || umask 077

	# Parse options
	while :; do
		# Separate option from value:
		opt="${1%%=*}"
		val="${1#*=}"

		# Empty values are not allowed unless expected
		unset -v is_empty empty_ok
		# eg: '--batch'
		[ "$opt" = "$val" ] && is_empty=1
		# eg: '--pki-dir='
		[ "$val" ] || is_empty=1


		case "${opt}" in

		''|help|-h|--help|--usage)
			cmd='help'
			break
		;;
		-V)
			cmd='version'
			break
		;;
		--batch)
			empty_ok=1
			export EASYTLS_BATCH=1
		;;
		-p|--pki-dir)
			export EASYRSA_PKI="${val}"
		;;
		--vars)
			export EASYRSA_VARS_FILE="${val}"
		;;
		-v|--verbose)
			empty_ok=1
			export EASYTLS_VERBOSE=1
		;;
		-s|--silent)
			empty_ok=1
			export EASYTLS_SILENT=1
		;;
		-i|--inline)
			empty_ok=1
			export EASYTLS_BINLINE=1
		;;
		--dh)
			export EASYRSA_DH_FILE="${val}"
		;;
		-g|--custom-group)
			export TLSKEY_CUSTOM_GRP="${val}"
		;;
		-k|--subkey|--sub-key-name)
			export TLSKEY_SUBNAME="${val}"
		;;
		-r|--ss-peer-fingerprint)
			export EASYTLS_PEER_FPR="${val}"
		;;
		-w|--ss-pass)
			empty_ok=1
			export EASYTLS_SS_PASSWORD=1
		;;
		-c|--ss-curve)
			export EASYTLS_EC_CURVE="${val}"
		;;
		-a|--ss-age)
			export EASYTLS_SS_AGE="${val}"
		;;
		-n|--no-auto-check)
			empty_ok=1
			AUTO_CHECK_DISABLED=1
		;;
		-y|--why-disable-file-hash)
			empty_ok=1
			FILE_HASH_DISABLED=1
		;;
		-t|--tmp-dir)
			EASYTLS_tmp_dir="${val}"
		;;
		-b|--base-dir)
			EASYTLS_base_dir="${val}"
		;;
		-o|--ovpnbin-dir)
			EASYTLS_ovpnbin_dir="${val}"
		;;
		-e|--ersabin-dir)
			EASYTLS_ersabin_dir="${val}"
		;;
		-*)
			fatal_opt "Unknown option: ${opt}"
			shift
			continue
		;;
		*)	break
		esac

		# fatal error when no value was provided
		if [ "$is_empty" ]; then
			[ "$empty_ok" ] || die "Missing value to option: $opt"
		fi

		shift
	done

	# Verify binary dependencies are present
	external_deps || die "external_deps"

	# Die on fatal opt error
	fatal_opt || die "fatal_opt"

	# Intelligent env-var detection and auto-loading:
	vars_setup || die "vars_setup"

	# Register cleanup on EXIT
	trap "cleanup" EXIT
	# When SIGHUP, SIGINT, SIGQUIT, SIGABRT and SIGTERM,
	# explicitly exit to signal EXIT (non-bash shells)
	trap "exit 1" 1
	trap "exit 2" 2
	trap "exit 3" 3
	trap "exit 6" 6
	trap "exit 14" 15

	# determine how we were called, then hand off to the function responsible
	# Notice, 'help' and 'version' are set above, so do not over-write
	cmd="${cmd:-${1}}"
	[ -z "${1}" ] || shift # scrape off command

	# Wrap in 'while' in order to 'break' from 'case'
	while :; do
	case "${cmd}" in
	# case 1

	''|help|-h|--help|--usage)
		unset -v EASYTLS_SILENT
		cmd_help "${1}" || die "cmd_help"
		AUTO_CHECK_DISABLED=1
		EASYTLS_verboff=1
		skip_master_hash=1
	;;
	ver|version)
		unset -v EASYTLS_SILENT
		[ "$EASYTLS_VERBOSE" ] && verify_openssl
		easytls_version || die "easytls_version"
		AUTO_CHECK_DISABLED=1
		EASYTLS_verboff=1
		skip_master_hash=1
	;;
	vhw)
		unset -v EASYTLS_SILENT
		if hw_addr_hex_check "$@"; then
			print "VALID: ${*}"
		else
			print "Invalid HW address: ${*}"
			exit 1
		fi
		AUTO_CHECK_DISABLED=1
		EASYTLS_verboff=1
		skip_master_hash=1
	;;
	vip)
		unset -v EASYTLS_SILENT
		if validate_ip_address "$@"; then
			print "VALID: ${valid_octets}/${mask_len}"
		else
			print "*** Invalid IP: ${*} ***"
			exit 1
		fi
		AUTO_CHECK_DISABLED=1
		EASYTLS_verboff=1
		skip_master_hash=1
	;;
	v4ip)
		unset -v EASYTLS_SILENT
		if validate_ip4_data "$@"; then
			print "VALID: ${valid_octets}/${mask_len}"
		else
			print "*** Invalid IP4: ${*} ***"
			exit 1
		fi
		AUTO_CHECK_DISABLED=1
		EASYTLS_verboff=1
		skip_master_hash=1
	;;
	x4ip)
		unset -v EASYTLS_SILENT
		if expand_ip4_address "$@"; then
			print "VALID: ${valid_octets}/${mask_len}"
		else
			print "*** Invalid IP4: ${*} ***"
			exit 1
		fi
		AUTO_CHECK_DISABLED=1
		EASYTLS_verboff=1
		skip_master_hash=1
	;;
	v6ip)
		unset -v EASYTLS_SILENT
		if validate_ip6_data "$@"; then
			print "VALID: ${full_valid_ip6_addr}"
		else
			print "*** Invalid IP6: ${*} ***"
			exit 1
		fi
		AUTO_CHECK_DISABLED=1
		EASYTLS_verboff=1
		skip_master_hash=1
	;;
	x6ip)
		unset -v EASYTLS_SILENT
		if expand_ip6_address "$@"; then
			print "* full_valid_hextets: ${full_valid_hextets}"
			print "* full_subnet_addr6 : ${full_subnet_addr6}"
		else
			invalid_address "$?"
			print "*** Invalid IP6: ${*} ***"
			exit 1
		fi
		AUTO_CHECK_DISABLED=1
		EASYTLS_verboff=1
		skip_master_hash=1
	;;
	# Do init before locking and update Master hash on exit
	init|init-tls)
		init_tls "$@" || die "init_tls"
		AUTO_CHECK_DISABLED=1
		EASYTLS_verboff=1
	;;
	*)
		# Create lock-file - Does not require a time-out
		if [ -d "${EASYTLS_DATA_DIR}" ]; then
			# data dir exists, create lock file directory
			if "${EASYTLS_MKDIR}" "${EASYTLS_TEMP_LOCK}"; then
				easytls_verbose "Lock acquired"
			else
				die "acquire lock-file"
			fi
		else
			: # Easy-TLS is not initialised
		fi

		# Load config options - Does not change command line options
		config_use || die "config_use"
		[ -z "${EASYTLS_NO_CA}" ] || easytls_verbose "No-CA mode"

		# Easy-TLS Upgrade
		# shellcheck disable=SC2249 # (info): default *) case
		case "${cmd}" in
		upgrade)
			# Special case for Old to New Easy-TLS
			# Otherwise verify_tls_init() will fail
			unset -v FILE_HASH_DISABLED
			easytls_upgrade "$@" || die "easytls_upgrade"
			AUTO_CHECK_DISABLED=1
			EASYTLS_verboff=1
			break
		esac

		# Must have init TLS now - and PKI when not No-CA mode
		if verify_tls_init; then
			if [ -z "${EASYTLS_NO_CA}" ]; then
				verify_pki_init || {
					error_msg "Easy-RSA has not been initialised."
					die "main - verify_pki_init"
					}
			fi
		else
			error_msg "Easy-TLS has not been initialised."
			die "main - verify_tls_init"
		fi

		# Rehash all file hashes
		# shellcheck disable=SC2249 # (info): default *) case
		case "${cmd}" in
		rehash)
			# If hashes have unsynced
			easytls_rehash "$@" || die "easytls_rehash"
			break
		esac

		# Master hash tests
		# shellcheck disable=SC2249 # (info): default *) case
		case "${cmd}" in
		gmh|generate-master-hash)
			return_hashes=1
			generate_master_hash "$@" || die "generate_master_hash"
			easytls_verbose
			print "generated_master_hash: ${generated_master_hash}"
			easytls_verbose
			unset -v return_hashes=1
			AUTO_CHECK_DISABLED=1
			skip_master_hash=1
			break
		;;
		smh|save-master-hash)
			return_hashes=1
			verify_master_hash "$@" || die "verify_faster_hash"
			easytls_verbose
			print "generated_master_hash: ${generated_master_hash}"
			print "saved_master_hash    : ${saved_master_hash}"
			print
			print "NOTICE: The two values above must be identical,"
			print "        otherwise, your Master hash is corrupted."
			confirm "* Over-write your current Master hash file ? " "yes" \
				"Warning:

  This will write {generated_master_hash} to your current Master hash file!"
			update_master_hash=1
			save_master_hash "$@" || die "save_master_hash"
			print "save_master_hash: ${generated_master_hash}"
			easytls_verbose
			unset -v return_hashes=1
			AUTO_CHECK_DISABLED=1
			skip_master_hash=1
			break
		;;
		vmh|verify-master-hash)
			return_hashes=1
			verify_master_hash "$@" || die "verify_master_hash"
			easytls_verbose
			print "generated_master_hash: ${generated_master_hash}"
			print "saved_master_hash    : ${saved_master_hash}"
			easytls_verbose
			unset -v return_hashes=1
			AUTO_CHECK_DISABLED=1
			skip_master_hash=1
			break
		esac

		# Verify me!
		# Get a valid hash, at all costs..
		verify_master_hash || die "verify_master_hash"
		# From this point forth, ALL hashes should be considered valid
		# Except Master hash
		disable_validate_hash=1

		# Set "Temp-Dir of last resort", if not already set by config
		if [ -n "${EASYTLS_FOR_WINDOWS}" ]; then
			set_var EASYTLS_tmp_dir "${host_drv}:/Windows/Temp"
			[ -d "${EASYTLS_tmp_dir}" ] || {
				help_note="
 * Example: ./easytls config temp.dir ${host_drv}:/Windows/Temp"
				die "Missing Temp-Dir: ${EASYTLS_tmp_dir}"
				}
		else
			set_var EASYTLS_tmp_dir "/tmp"
			[ -d "${EASYTLS_tmp_dir}" ] || {
				help_note="
 * Example: ./easytls config temp.dir /tmp"
				die "Missing Temp-Dir: ${EASYTLS_tmp_dir}"
				}
		fi

		# Generate valid date/time now and only once
		full_date="$("${EASYTLS_DATE}" '+%s %Y/%m/%d-%H:%M:%S')" || \
			die "Failed to get date"
		local_date_ascii="${full_date##* }"
		local_time_unix="${full_date%% *}"

		# Commands which can run with or without a PKI
		case "${cmd}" in
		# case 2

		cf|cfg|config)
			easytls_config "$@" || die "easytls_config"
		;;
		build)
			unset -v EASYTLS_SILENT
			interactive_build || die "interactive_build"
		;;
		inline)
			unset -v EASYTLS_SILENT
			interactive_inline || die "interactive_inline"
		;;
		remove)
			unset -v EASYTLS_SILENT
			interactive_remove || die "interactive_remove"
		;;
		script)
			unset -v EASYTLS_SILENT
			interactive_scripts || die "interactive_scripts"
		;;
		ss|selfsign)
			unset -v EASYTLS_SILENT
			interactive_selfsign || die "interactive_selfsign"
		;;
		bta|build-tls-auth)
			build_tls_auth "$@" || die "build_tls_auth"
		;;
		btc|build-tls-crypt)
			build_tls_crypt_v1 "$@" || die "build_tls_crypt_v1"
		;;
		ita|inline-tls-auth)
			inline_tls_auth "$@" || die "inline_tls_auth"
		;;
		itc|inline-tls-crypt)
			inline_tls_crypt_v1 "$@" || die "inline_tls_crypt_v1"
		;;
		bc2s|btc2s|btv2s|btcv2s|build-tls-crypt-v2-server)
			build_tls_crypt_v2_server "$@" || die "build_tls_crypt_v2_server"
		;;
		bc2c|btc2c|btv2c|btcv2c|build-tls-crypt-v2-client)
			build_tls_crypt_v2_client "$@" || die "build_tls_crypt_v2_client"
		;;
		itc2|itv2|itcv2|inline-tls-crypt-v2)
			inline_tls_crypt_v2 "$@" || die "inline_tls_crypt_v2"
		;;
		# GROUP Server
		bc2gs|btc2gs|btv2gs|btcv2gs|build-tls-crypt-v2-group-server)
			build_tls_cv2_group_server "$@" || \
				die "build_tls_crypt_v2_group_server"
		;;
		ic2gs|itc2gs|itcv2gs|inline-tls-crypt-v2-group-server)
			inline_tls_cv2_group_server "$@" || \
				die "inline_tls_crypt_v2_group_server"
		;;
		# GROUP Client
		bc2gc|btc2gc|btv2gc|btcv2gc|build-tls-crypt-v2-group-client)
			build_tls_cv2_group_client "$@" || \
				die "build_tls_crypt_v2_group_client"
		;;
		ic2gc|itc2gc|itcv2gc|inline-tls-crypt-v2-group-client)
			inline_tls_cv2_group_client "$@" || \
				die "inline_tls_crypt_v2_group_server"
		;;
		is|inline-show)
			inline_show "$@" || die "inline_show"
			skip_master_hash=1
		;;
		ri|ril|remove-inline)
			remove_inline "$@" || die "remove_inline"
		;;
		rgi|rgil|remove-group-inline)
			remove_group_inline "$@" || die "remove_group_inline"
		;;
		rt|rk|rtk|remove-tlskey)
			remove_tlskey "$@" || die "remove_tlskey"
		;;
		rgt|rgk|rgtk|remove-group-tlskey)
			remove_group_tlskey "$@" || die "remove_group_tlskey"
		;;
		ix|inline-expire)
			inline_expire "$@" || die "inline_expire"
			skip_master_hash=1
		;;
		cx|cert-expire)
			cert_expire "$@" || die "cert_expire"
			skip_master_hash=1
		;;
		d|disable)
			disabled_list_manager "disable" "$@" || die "disabled_list_manager"
		;;
		e|enable)
			disabled_list_manager "enable" "$@" || die "disabled_list_manager"
		;;
		*)
			if [ -n "${EASYTLS_NO_CA}" ]; then
				# No-CA use these versions of commands
				case "${cmd}" in
				sss|self-sign-server)
					build_self_sign 'server' "$@" || die "self-sign-server"
				;;
				ssc|self-sign-client)
					build_self_sign 'client' "$@" || die "self-sign-client"
				;;
				s|status)
					noca_status "$@" || die "noca_status"
					skip_master_hash=1
				;;
				*)
				print "Unknown command '${cmd}'. Run without commands for help."
					skip_master_hash=1
				esac # => No-CA mode
			else
				# CA mode
				case "${cmd}" in
				sid|save-id)
					save_id || die "save_id"
				;;
				s|status)
					status "$@" || die "status"
					unset -v status_disabled_auto_check
					skip_master_hash=1
				;;
				irn|irw|inline-renew)
					inline_renew "$@" || die "inline_renew"
				;;
				inline-index-rebuild)
					inline_index_rebuild "$@" || die "inline_index_rebuild"
					skip_master_hash=1
				;;
				*)
				print "Unknown command '${cmd}'. Run without commands for help."
					skip_master_hash=1
				esac # CA mode
			fi
		esac # case 2
	esac # case 1
	break
	done # 'while' wrapper

	# EasyTLS auto-check
	easytls_auto_check || die "easytls_auto_check fail"

	# save me, only if update_master_hash=1 AND NOT skip_master_hash
	if [ -z "${skip_master_hash}" ]; then
		# Get a valid hash, at all costs..
		unset -v disable_validate_hash
		save_master_hash || die "main - save_master_hash"
	else
		[ -z "${update_master_hash}" ] || die "Master hash status undefined"
	fi
} # => main ()

main "$@" || die "main $*"

# Do cleanup
